feat(blog): blog list integration

This commit is contained in:
RizqiSyahrendra 2025-02-03 12:13:56 +07:00
parent e7268f644d
commit 313a576106
15 changed files with 323 additions and 483 deletions

View File

@ -1,44 +1,17 @@
import BlogComments from "@/components/BlogComments"; import BlogComments from "@/components/Blogs/BlogComments";
import BlogWidget from "@/components/BlogWidget"; import BlogWidget from "@/components/Blogs/BlogWidget";
import CommentForm from "@/components/CommentForm"; import CommentForm from "@/components/CommentForm";
import { allBlogs } from "@/data/blogs"; import { fetchBlogDetail } from "@/services/payload/blog";
import payloadConfig from "@/payload.config";
import { formatDate } from "@/utils/datetime";
import Image from "next/image";
import { getPayload } from "payload";
import { RichText } from "@payloadcms/richtext-lexical/react"; import { RichText } from "@payloadcms/richtext-lexical/react";
import { Metadata } from "next"; import { Metadata } from "next";
import Image from "next/image";
async function fetchBlog(slug: string) {
const payload = await getPayload({ config: payloadConfig });
const blogDataQuery = await payload.find({
collection: "blogs",
where: {
slug: { equals: slug },
},
limit: 1,
pagination: false,
});
if (!blogDataQuery?.docs?.[0]) return null;
const data = blogDataQuery?.docs?.[0];
const createdAt = formatDate(data.createdAt);
const imgUrl = typeof data.img !== "number" ? (data?.img?.url ?? "") : "";
return {
data,
createdAt,
imgUrl,
};
}
export async function generateMetadata({ export async function generateMetadata({
params, params,
}: { }: {
params: { slug: string }; params: { slug: string };
}): Promise<Metadata> { }): Promise<Metadata> {
const blog = await fetchBlog(params.slug); const blog = await fetchBlogDetail(params.slug);
if (!blog) { if (!blog) {
return { return {
@ -65,17 +38,16 @@ export async function generateMetadata({
}; };
} }
export default async function BlogRead({ export default async function SingleBlogPage({
params, params,
}: { }: {
params: Promise<{ slug: string }>; params: Promise<{ slug: string }>;
}) { }) {
const slug = (await params).slug; const slug = (await params).slug;
const data = await fetchBlog(slug); const data = await fetchBlogDetail(slug);
if (!data) return <></>; if (!data) return <></>;
const blog = allBlogs.filter((elm) => elm.id == 34)[0] || allBlogs[0];
return ( return (
<> <>
<section <section
@ -111,22 +83,11 @@ export default async function BlogRead({
{data.createdAt} {data.createdAt}
</a> </a>
</div> </div>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-user size-16" />
<span className="visually-hidden">Author:</span> John Doe
</a>
</div>
<div className="d-inline-block me-3"> <div className="d-inline-block me-3">
<i className="mi-folder size-16" /> <i className="mi-folder size-16" />
<span className="visually-hidden">Categories:</span> <span className="visually-hidden">Categories:</span>{" "}
<a href="#">Design</a>, <a href="#">Branding</a> <a href="#">Design</a>, <a href="#">Branding</a>
</div> </div>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-message size-16" /> 5 Comments
</a>
</div>
</div> </div>
{/* End Author, Categories, Comments */} {/* End Author, Categories, Comments */}
</div> </div>
@ -158,36 +119,6 @@ export default async function BlogRead({
</div> </div>
</div> </div>
{/* End Post */} {/* End Post */}
{/* Comments */}
<div className="mb-80 mb-xs-40">
<h4 className="blog-page-title">
Comments <small className="number">(3)</small>
</h4>
<ul className="media-list comment-list clearlist">
<BlogComments />
</ul>
</div>
{/* End Comments */}
{/* Add Comment */}
<div className="mb-80 mb-xs-40">
<h4 className="blog-page-title">Leave a comment</h4>
{/* Form */}
<CommentForm />
{/* End Form */}
</div>
{/* End Add Comment */}
{/* Prev/Next Post */}
<div className="clearfix mt-40">
<a href="#" className="blog-item-more circle left">
<i className="mi-chevron-left" />
&nbsp;Prev post
</a>
<a href="#" className="blog-item-more circle right">
Next post&nbsp;
<i className="mi-chevron-right" />
</a>
</div>
{/* End Prev/Next Post */}
</div> </div>
{/* End Content */} {/* End Content */}
</div> </div>

View File

@ -1,19 +1,22 @@
import Blogs from "@/components/Blogs"; import Blogs from "@/components/Blogs/Blogs";
import NewsletterForm from "@/components/NewsletterForm";
import { archiveLinks } from "@/data/archieve"; import { archiveLinks } from "@/data/archieve";
import { categories } from "@/data/categories"; import { categories } from "@/data/categories";
import { tags } from "@/data/tags"; import { tags } from "@/data/tags";
import { sanitizePageNumber } from "@/utils/sanitize";
import Image from "next/image"; import Image from "next/image";
export const metadata = { export const metadata = {
title: title: "Blogs | Cochise Oncology",
"Slick Blogs || Resonance &mdash; One & Multi Page React Nextjs Creative Template", description: "Blogs | Cochise Oncology",
description:
"Resonance &mdash; One & Multi Page React Nextjs Creative Template",
}; };
const onePage = false;
const dark = false; export default async function BlogPage({
export default function SlickBlogPage() { searchParams,
}: {
searchParams?: { page?: string };
}) {
const page = sanitizePageNumber(searchParams?.page);
return ( return (
<> <>
<section <section
@ -47,7 +50,7 @@ export default function SlickBlogPage() {
</div> </div>
</section> </section>
<section className="page-section" id="blog"> <section className="page-section" id="blog">
<Blogs /> <Blogs page={page} />
</section> </section>
<> <>
@ -58,7 +61,7 @@ export default function SlickBlogPage() {
<section className="page-section"> <section className="page-section">
<div className="container relative"> <div className="container relative">
<div className="row mt-n60"> <div className="row mt-n60">
<div className="col-sm-6 col-lg-3 mt-60"> <div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */} {/* Widget */}
<div className="widget mb-0"> <div className="widget mb-0">
<h3 className="widget-title">Categories</h3> <h3 className="widget-title">Categories</h3>
@ -77,7 +80,7 @@ export default function SlickBlogPage() {
</div> </div>
{/* End Widget */} {/* End Widget */}
</div> </div>
<div className="col-sm-6 col-lg-3 mt-60"> <div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */} {/* Widget */}
<div className="widget mb-0"> <div className="widget mb-0">
<h3 className="widget-title">Tags</h3> <h3 className="widget-title">Tags</h3>
@ -93,7 +96,7 @@ export default function SlickBlogPage() {
</div> </div>
{/* End Widget */} {/* End Widget */}
</div> </div>
<div className="col-sm-6 col-lg-3 mt-60"> <div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */} {/* Widget */}
<div className="widget mb-0"> <div className="widget mb-0">
<h3 className="widget-title">Archive</h3> <h3 className="widget-title">Archive</h3>
@ -111,18 +114,6 @@ export default function SlickBlogPage() {
</div> </div>
{/* End Widget */} {/* End Widget */}
</div> </div>
<div className="col-sm-6 col-lg-3 mt-60">
{/* Widget */}
<div className="widget mb-0">
<h3 className="widget-title">Newsletter</h3>
<div className="widget-body">
<div className="widget-text clearfix">
<NewsletterForm />
</div>
</div>
</div>
{/* End Widget */}
</div>
</div> </div>
</div> </div>
</section> </section>

View File

@ -1,202 +0,0 @@
import BlogComments from "@/components/BlogComments";
import BlogWidget from "@/components/BlogWidget";
import CommentForm from "@/components/CommentForm";
import { allBlogs } from "@/data/blogs";
import Image from "next/image";
export const metadata = {
title:
"Slick Blogs Single || Resonance &mdash; One & Multi Page React Nextjs Creative Template",
description:
"Resonance &mdash; One & Multi Page React Nextjs Creative Template",
};
export default async function asyncSlickBlogSinglePage(
params: Promise<{ id: number }>
) {
const id = (await params).id;
const blog = allBlogs.filter((elm) => elm.id == id)[0] || allBlogs[0];
return (
<>
<section
className="page-section bg-gradient-gray-light-1 bg-scroll overflow-hidden"
id="home"
>
{/* <!-- Background Shape --> */}
<div className="bg-shape-1 wow fadeIn">
<Image
src="/assets/images/demo-fancy/bg-shape-1.svg"
width={1443}
height={844}
alt=""
/>
</div>
{/* <!-- End Background Shape --> */}
<div className="container position-relative pt-sm-40 text-center">
<div className="row">
<div className="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
<h1 className="hs-title-10 mb-10 wow fadeInUp">
{/* @ts-ignore */}
{blog?.title || blog?.postTitle}
</h1>
{/* Author, Categories, Comments */}
<div
className="blog-item-data mb-0 wow fadeIn"
data-wow-delay="0.2s"
>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-clock size-16" />
<span className="visually-hidden">Date:</span> December 25
</a>
</div>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-user size-16" />
<span className="visually-hidden">Author:</span> John Doe
</a>
</div>
<div className="d-inline-block me-3">
<i className="mi-folder size-16" />
<span className="visually-hidden">Categories:</span>
<a href="#">Design</a>, <a href="#">Branding</a>
</div>
<div className="d-inline-block me-3">
<a href="#">
<i className="mi-message size-16" /> 5 Comments
</a>
</div>
</div>
{/* End Author, Categories, Comments */}
</div>
</div>
</div>
</section>
<>
{/* Section */}
<section className="page-section">
<div className="container relative">
<div className="row">
{/* Content */}
<div className="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
{/* Post */}
<div className="blog-item mb-80 mb-xs-40">
<div className="blog-item-body">
<div className="mb-40 mb-xs-30">
<Image
src="/assets/images/demo-fancy/blog/post-prev-3-large.jpg"
alt="Image Description"
width={1350}
height={759}
className="round"
/>
</div>
<p>
Morbi lacus massa, euismod ut turpis molestie, tristique
sodales est. Integer sit amet mi id sapien tempor molestie
in nec massa. Fusce non ante sed lorem rutrum feugiat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris non laoreet dui. Morbi lacus massa, euismod ut
turpis molestie, tristique sodales est. Integer sit amet
mi id sapien tempor molestie in nec massa.
</p>
<p>
Fusce non ante sed lorem rutrum feugiat. Vestibulum
pellentesque, purus ut&nbsp;dignissim consectetur, nulla
erat ultrices purus, ut&nbsp;consequat sem elit non sem.
Morbi lacus massa, euismod ut turpis molestie, tristique
sodales est. Integer sit amet mi id sapien tempor molestie
in nec massa. Fusce non ante sed lorem rutrum feugiat.
</p>
<blockquote>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer posuere erat a&nbsp;ante. Vestibulum
pellentesque, purus ut dignissim consectetur, nulla erat
ultrices purus.
</p>
<footer>
Someone famous in
<cite title="Source Title"> Source Title </cite>
</footer>
</blockquote>
<p>
Praesent ultricies ut ipsum non laoreet. Nunc ac
<a href="#">ultricies</a> leo. Nulla ac ultrices arcu.
Nullam adipiscing lacus in consectetur posuere. Nunc
malesuada tellus turpis, ac pretium orci molestie vel.
Morbi lacus massa, euismod ut turpis molestie, tristique
sodales est. Integer sit amet mi id sapien tempor molestie
in nec massa. Fusce non ante sed lorem rutrum feugiat.
</p>
<ul>
<li>First item of the list</li>
<li>Second item of the list</li>
<li>Third item of the list</li>
</ul>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris non laoreet dui. Morbi lacus massa, euismod ut
turpis molestie, tristique sodales est. Integer sit amet
mi id sapien tempor molestie in nec massa. Fusce non ante
sed lorem rutrum feugiat. Vestibulum pellentesque, purus
ut&nbsp;dignissim consectetur, nulla erat ultrices purus,
ut&nbsp;consequat sem elit non sem.
</p>
</div>
</div>
{/* End Post */}
{/* Comments */}
<div className="mb-80 mb-xs-40">
<h4 className="blog-page-title">
Comments <small className="number">(3)</small>
</h4>
<ul className="media-list comment-list clearlist">
<BlogComments />
</ul>
</div>
{/* End Comments */}
{/* Add Comment */}
<div className="mb-80 mb-xs-40">
<h4 className="blog-page-title">Leave a comment</h4>
{/* Form */}
<CommentForm />
{/* End Form */}
</div>
{/* End Add Comment */}
{/* Prev/Next Post */}
<div className="clearfix mt-40">
<a href="#" className="blog-item-more circle left">
<i className="mi-chevron-left" />
&nbsp;Prev post
</a>
<a href="#" className="blog-item-more circle right">
Next post&nbsp;
<i className="mi-chevron-right" />
</a>
</div>
{/* End Prev/Next Post */}
</div>
{/* End Content */}
</div>
</div>
</section>
{/* End Section */}
{/* Divider */}
<hr className="mt-0 mb-0" />
{/* End Divider */}
{/* Section */}
<section className="page-section">
<div className="container relative">
<div className="row mt-n60">
<BlogWidget
inputClass="newsletter-field form-control input-md circle mb-10"
btnClass="btn btn-mod btn-color btn-medium btn-circle btn-hover-anim form-control"
/>
</div>
</div>
</section>
</>
</>
);
}

View File

@ -1,63 +0,0 @@
import Pagination from "@/components/Pagination";
import { blogs11 } from "@/data/blogs";
import Image from "next/image";
import Link from "next/link";
import React from "react";
export default function Blogs() {
return (
<div className="container position-relative">
{/* Blog Posts Grid */}
<div className="row mt-n50 mb-50">
{/* Post Item */}
{blogs11.map((elm, i) => (
<div
key={i}
className="post-prev-3 col-12 col-lg-10 offset-lg-1 col-xl-6 offset-xl-0 mt-50"
>
<div className="post-prev-3-container d-block d-sm-flex">
<div className="post-prev-3-img">
<Link href={`/slick-blog-single/${elm.id}`}>
<Image
width={400}
height={488}
src={elm.imgSrc}
alt="Add Image Description"
/>
</Link>
</div>
<div className="post-prev-3-intro">
<h4 className="post-prev-3-title">
<Link href={`/slick-blog-single/${elm.id}`}>{elm.title}</Link>
</h4>
<div className="post-prev-3-text">{elm.text}</div>
<div className="post-prev-3-info clearfix">
<div className="float-start">
<a href="#">
<Image
className="post-prev-3-author-img"
width={30}
height={30}
src={elm.authorImg}
alt="Image Description"
/>
</a>
<a href="#">{elm.author}</a>
</div>
<div className="float-end">
<a href="#">{elm.date}</a>
</div>
</div>
</div>
</div>
</div>
))}
{/* End Post Item */}
</div>
{/* End Blog Posts Grid */}
{/* Pagination */}
<Pagination />
{/* End Pagination */}
</div>
);
}

View File

@ -0,0 +1,63 @@
import Image from "next/image";
import Link from "next/link";
export interface BlogCardItemProps {
data: {
slug: string;
title: string;
img?: {
url: string;
alt: string;
};
contentPreview: string;
author?: {
name: string;
img: string;
};
date: string;
};
}
export function BlogCardItem({ data }: BlogCardItemProps) {
return (
<div className="post-prev-3 col-12 col-lg-10 offset-lg-1 col-xl-6 offset-xl-0 mt-50">
<div className="post-prev-3-container d-block d-sm-flex">
<div className="post-prev-3-img">
<Link href={`/blog/${data.slug}`}>
<Image
width={400}
height={488}
src={data?.img?.url ?? ""}
alt={data?.img?.alt ?? ""}
/>
</Link>
</div>
<div className="post-prev-3-intro">
<h4 className="post-prev-3-title">
<Link href={`/blog/${data.slug}`}>{data.title}</Link>
</h4>
<div className="post-prev-3-text">{data.contentPreview}</div>
<div className="post-prev-3-info clearfix">
{!!data?.author?.name && (
<div className="float-start">
<a href="#">
<Image
className="post-prev-3-author-img"
width={30}
height={30}
src={data.author?.img ?? "#"}
alt="Image Description"
/>
</a>
<a href="#">{data.author.name}</a>
</div>
)}
<div className={!!data?.author?.name ? "float-end" : "float-start"}>
<a href="#">{data.date}</a>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -10,7 +10,7 @@ export default function BlogWidget({
}) { }) {
return ( return (
<> <>
<div className="col-sm-6 col-lg-3 mt-60"> <div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */} {/* Widget */}
<div className="widget mb-0"> <div className="widget mb-0">
<h3 className="widget-title">Categories</h3> <h3 className="widget-title">Categories</h3>
@ -29,7 +29,7 @@ export default function BlogWidget({
</div> </div>
{/* End Widget */} {/* End Widget */}
</div> </div>
<div className="col-sm-6 col-lg-3 mt-60"> <div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */} {/* Widget */}
<div className="widget mb-0"> <div className="widget mb-0">
<h3 className="widget-title">Tags</h3> <h3 className="widget-title">Tags</h3>
@ -45,7 +45,7 @@ export default function BlogWidget({
</div> </div>
{/* End Widget */} {/* End Widget */}
</div> </div>
<div className="col-sm-6 col-lg-3 mt-60"> <div className="col-sm-6 col-lg-4 mt-60">
{/* Widget */} {/* Widget */}
<div className="widget mb-0"> <div className="widget mb-0">
<h3 className="widget-title">Archive</h3> <h3 className="widget-title">Archive</h3>
@ -63,52 +63,6 @@ export default function BlogWidget({
</div> </div>
{/* End Widget */} {/* End Widget */}
</div> </div>
<div className="col-sm-6 col-lg-3 mt-60">
<div className="widget mb-0">
<h3 className="widget-title">Newsletter</h3>
<div className="widget-body">
<div className="widget-text clearfix">
<form
onSubmit={(e) => e.preventDefault()}
className="form"
id="mailchimp"
>
<div className="mb-20">Stay informed with our newsletter.</div>
<div className="mb-20">
<input
placeholder="Enter your email"
className={inputClass}
type="email"
pattern=".{5,100}"
required
aria-required="true"
/>
<button type="submit" className={btnClass}>
<span>Subscribe</span>
</button>
</div>
<div className="form-tip">
<i className="icon-info size-16" aria-hidden="true"></i>
By sending the form you agree to the
<a href="#">Terms &amp; Conditions</a> and
<a href="#">Privacy Policy</a>.
</div>
<div
id="subscribe-result"
role="region"
aria-live="polite"
aria-atomic="true"
></div>
</form>
</div>
</div>
</div>
</div>
</> </>
); );
} }

View File

@ -0,0 +1,56 @@
import Pagination from "@/components/Pagination";
import { fetchBlog } from "@/services/payload/blog";
import { BlogCardItem } from "./BlogCardItem";
import { sanitizeBlogContentIntoStringPreview } from "@/utils/sanitize";
export interface BlogsProps {
page: number;
}
export default async function Blogs({ page }: BlogsProps) {
const data = await fetchBlog(page);
if (!data?.totalDocs) return <></>;
const handleClickPage = (clickedPage: number) => {
if (typeof window === "undefined") return;
window.location.href = `/blog/?page=${clickedPage}`;
};
return (
<div className="container position-relative">
{/* Blog Posts Grid */}
<div className="row mt-n50 mb-50">
{/* Post Item */}
{data.formattedData.map((blog, i) => (
<BlogCardItem
key={i}
data={{
slug: blog.slug,
title: blog.title,
contentPreview: sanitizeBlogContentIntoStringPreview(
blog.content
),
date: blog.createdAtFormatted,
img: blog.imgFormatted,
}}
/>
))}
{/* End Post Item */}
</div>
{/* End Blog Posts Grid */}
{/* Pagination */}
{data.totalPages > 1 && (
<Pagination
page={data.page ?? 1}
hasNextPage={data.hasNextPage}
hasPreviousPage={data.hasPrevPage}
totalPages={data.totalPages}
onClickPage={handleClickPage}
/>
)}
{/* End Pagination */}
</div>
);
}

View File

@ -13,7 +13,7 @@ const links = [
import Image from "next/image"; import Image from "next/image";
import LanguageSelect from "./LanguageSelect"; import LanguageSelect from "./LanguageSelect";
export default function Header9({ links }: any) { export default function Header({ links }: any) {
return ( return (
<div className="main-nav-sub full-wrapper"> <div className="main-nav-sub full-wrapper">
{/* Logo (* Add your text or image to the link tag. Use SVG or PNG image format. {/* Logo (* Add your text or image to the link tag. Use SVG or PNG image format.

View File

@ -5,7 +5,7 @@ import Service from "./Service";
import Portfolio from "./Portfolio"; import Portfolio from "./Portfolio";
import Image from "next/image"; import Image from "next/image";
import Testimonials from "./Testimonials"; import Testimonials from "./Testimonials";
import Blog from "./Blog"; import Blog from "./Blogs/Blog";
import Newsletter from "./Newsletter"; import Newsletter from "./Newsletter";
import Contact from "./Contact"; import Contact from "./Contact";
import Link from "next/link"; import Link from "next/link";

View File

@ -1,19 +1,73 @@
"use client"; "use client";
import React, { useState } from "react";
export default function Pagination({ className }: { className?: string }) { import React from "react";
const [activePage, setActivePage] = useState(1); // Initialize active page
interface PaginationProps {
page: number;
hasPreviousPage: boolean;
hasNextPage: boolean;
totalPages: number;
onClickPage?: (page: number) => void;
}
export default function Pagination({
page,
hasPreviousPage,
hasNextPage,
totalPages,
onClickPage,
}: PaginationProps) {
const activePage = page;
// Function to handle page change // Function to handle page change
const handlePageChange = (page: number) => { const handlePageChange = (page: string | number) => {
setActivePage(page); if (typeof page === "string") return;
onClickPage?.(page);
};
const getPageNumbers = () => {
const pages = [];
const showEllipsisStart = activePage > 4;
const showEllipsisEnd = activePage < totalPages - 3;
if (totalPages <= 7) {
// Show all pages if total is 7 or less
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
// Always show first page
pages.push(1);
if (showEllipsisStart) {
pages.push("...");
}
// Show pages around current page
const start = showEllipsisStart ? Math.max(2, activePage - 1) : 2;
const end = showEllipsisEnd
? Math.min(totalPages - 1, activePage + 1)
: totalPages - 1;
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (showEllipsisEnd) {
pages.push("...");
}
// Always show last page
pages.push(totalPages);
}
return pages;
}; };
return ( return (
<div <div className={"pagination justify-content-center"}>
className={className ? className : "pagination justify-content-center"}
>
{/* Previous Page Button */} {/* Previous Page Button */}
{hasPreviousPage && (
<a <a
onClick={() => activePage > 1 && handlePageChange(activePage - 1)} onClick={() => activePage > 1 && handlePageChange(activePage - 1)}
className={activePage === 1 ? "disabled" : ""} className={activePage === 1 ? "disabled" : ""}
@ -21,58 +75,30 @@ export default function Pagination({ className }: { className?: string }) {
<i className="mi-chevron-left" /> <i className="mi-chevron-left" />
<span className="visually-hidden">Previous page</span> <span className="visually-hidden">Previous page</span>
</a> </a>
{/* Page Number 1 */}
<a
onClick={() => handlePageChange(1)}
className={activePage === 1 ? "active" : ""}
>
1
</a>
{/* Page Number 2 */}
<a
onClick={() => handlePageChange(2)}
className={activePage === 2 ? "active" : ""}
>
2
</a>
{/* Page Number 3 */}
<a
onClick={() => handlePageChange(3)}
className={activePage === 3 ? "active" : ""}
>
3
</a>
{activePage > 4 && activePage < 8 && (
<span className="no-active">...</span>
)} )}
{activePage > 3 && activePage < 8 && ( {getPageNumbers().map((page, key) => (
<a className={"active"}>{activePage}</a>
)}
{/* Ellipsis */}
<span className="no-active">...</span>
{activePage == 8 && <a className={"active"}>{8}</a>}
{/* Page Number 9 */}
<a <a
onClick={() => handlePageChange(9)} key={key}
className={activePage === 9 ? "active" : ""} onClick={() => handlePageChange(page)}
className={activePage === page ? "active" : ""}
> >
9 {page}
</a> </a>
))}
{/* Next Page Button */} {/* Next Page Button */}
{hasNextPage && (
<a <a
onClick={() => activePage < 9 && handlePageChange(activePage + 1)} onClick={() =>
className={activePage === 9 ? "disabled" : ""} activePage < totalPages && handlePageChange(activePage + 1)
}
className={activePage === totalPages ? "disabled" : ""}
> >
<i className="mi-chevron-right" /> <i className="mi-chevron-right" />
<span className="visually-hidden">Next page</span> <span className="visually-hidden">Next page</span>
</a> </a>
)}
</div> </div>
); );
} }

View File

@ -28,7 +28,7 @@ export const slickMultipages = [
{ href: "/slick-about", text: "About", class: "active" }, { href: "/slick-about", text: "About", class: "active" },
{ href: "/slick-services", text: "Services" }, { href: "/slick-services", text: "Services" },
{ href: "/slick-portfolio", text: "Portfolio" }, { href: "/slick-portfolio", text: "Portfolio" },
{ href: "/slick-blog", text: "Blog" }, { href: "/blog", text: "Blog" },
{ href: "/slick-contact", text: "Contact" }, { href: "/slick-contact", text: "Contact" },
]; ];
export const slickMultipagesDark = [ export const slickMultipagesDark = [
@ -36,7 +36,7 @@ export const slickMultipagesDark = [
{ href: "/slick-about-dark", text: "About", class: "active" }, { href: "/slick-about-dark", text: "About", class: "active" },
{ href: "/slick-services-dark", text: "Services" }, { href: "/slick-services-dark", text: "Services" },
{ href: "/slick-portfolio-dark", text: "Portfolio" }, { href: "/slick-portfolio-dark", text: "Portfolio" },
{ href: "/slick-blog-dark", text: "Blog" }, { href: "/blog", text: "Blog" },
{ href: "/slick-contact-dark", text: "Contact" }, { href: "/slick-contact-dark", text: "Contact" },
]; ];
export const slickOnepage = [ export const slickOnepage = [

View File

@ -0,0 +1,52 @@
import payloadConfig from "@/payload.config";
import { formatDate } from "@/utils/datetime";
import { getPayload } from "payload";
export async function fetchBlog(page: number = 1) {
const payload = await getPayload({ config: payloadConfig });
const blogDataQuery = await payload.find({
collection: "blogs",
page,
pagination: true,
});
const formattedData = blogDataQuery.docs.map((item) => {
return {
...item,
imgFormatted:
typeof item.img !== "number"
? { url: item?.img?.url ?? "", alt: item.img.alt }
: undefined,
createdAtFormatted: formatDate(item.createdAt),
};
});
return {
...blogDataQuery,
formattedData,
};
}
export async function fetchBlogDetail(slug: string) {
const payload = await getPayload({ config: payloadConfig });
const blogDataQuery = await payload.find({
collection: "blogs",
where: {
slug: { equals: slug },
},
limit: 1,
pagination: false,
});
if (!blogDataQuery?.docs?.[0]) return null;
const data = blogDataQuery?.docs?.[0];
const createdAt = formatDate(data.createdAt);
const imgUrl = typeof data.img !== "number" ? (data?.img?.url ?? "") : "";
return {
data,
createdAt,
imgUrl,
};
}

32
src/utils/sanitize.ts Normal file
View File

@ -0,0 +1,32 @@
import { Blog } from "@/payload-types";
export function sanitizePageNumber(page: any, defaultPage = 1): number {
const parsedPage = Number(page);
if (isNaN(parsedPage) || parsedPage < 1 || !Number.isInteger(parsedPage)) {
return defaultPage;
}
return parsedPage;
}
export function sanitizeBlogContentIntoStringPreview(data: Blog["content"]) {
// Find the first paragraph that has children with text
const firstParagraph = data.root.children.find(
(node) =>
node.type === "paragraph" &&
Array.isArray(node.children) &&
node.children.length > 0 &&
!!node.children?.[0]?.text
);
if (!firstParagraph) {
return "...";
}
// @ts-ignore
const text = firstParagraph.children?.[0]?.text ?? "";
// Limit to 100 characters
return `${text.length > 100 ? text.slice(0, 100) : text}...`;
}