feat: recent blog post related fetching
This commit is contained in:
parent
9f7360dbd0
commit
12e7941f6c
@ -1,3 +1,4 @@
|
||||
import ListOfRecentBlog from "@/components/blogs/ListOfRecentBlog";
|
||||
import HeroImage from "@/components/HeroImage";
|
||||
import { fetchBlogDetail } from "@/services/payload/blog";
|
||||
import { getDefaultMetadata } from "@/utils/metadata";
|
||||
@ -110,65 +111,31 @@ export default async function BlogDetail(props: { params: Promise<{ slug: string
|
||||
<span>Share this post</span>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon icon-circle icon-rounded icon-5 fa-facebook" href={shareUrl.facebook}></a>
|
||||
<a
|
||||
target="_blank"
|
||||
className="icon icon-circle icon-rounded icon-5 fa-facebook"
|
||||
href={shareUrl.facebook}
|
||||
></a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon icon-circle icon-rounded icon-6 fa-twitter" href={shareUrl.twitter}></a>
|
||||
<a
|
||||
target="_blank"
|
||||
className="icon icon-circle icon-rounded icon-6 fa-twitter"
|
||||
href={shareUrl.twitter}
|
||||
></a>
|
||||
</li>
|
||||
<li>
|
||||
<a className="icon icon-circle icon-rounded icon-4 fa-linkedin" href={shareUrl.linkedin}></a>
|
||||
<a
|
||||
target="_blank"
|
||||
className="icon icon-circle icon-rounded icon-4 fa-linkedin"
|
||||
href={shareUrl.linkedin}
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="post-simple-group">
|
||||
<div className="post-simple-group-title">
|
||||
<h6>Recent Posts</h6>
|
||||
</div>
|
||||
<div className="post-simple-group-divider"></div>
|
||||
<div className="row row-30">
|
||||
<div className="col-sm-6">
|
||||
<article className="post-simple">
|
||||
<div className="post-simple-img">
|
||||
<Image src="/images/blog-post-03-736x540.jpg" alt="" width="736" height="540" />
|
||||
</div>
|
||||
<div className="post-simple-body">
|
||||
<div className="post-simple-title">
|
||||
<h4>
|
||||
<a href="#">Turks and Caicos Villa to be Sold for Record $7.6M</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div className="post-simple-time">
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<a className="time" href="#">
|
||||
March 15, 2021
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<article className="post-simple">
|
||||
<div className="post-simple-img">
|
||||
<Image src="/images/blog-post-04-736x540.jpg" alt="" width="736" height="540" />
|
||||
</div>
|
||||
<div className="post-simple-body">
|
||||
<div className="post-simple-title">
|
||||
<h4>
|
||||
<a href="#">How We Build a Better LA for Fifth Year in a Row</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div className="post-simple-time">
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<a className="time" href="#">
|
||||
March 15, 2021
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ListOfRecentBlog currentBlogId={blog?.data?.id} />
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
@ -3,12 +3,47 @@ import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
type CardBlogProps = {
|
||||
colorPreset?: 1 | 2;
|
||||
isDescriptionVisible?: boolean;
|
||||
data: BlogData;
|
||||
};
|
||||
|
||||
export default function CardBlog({ data }: CardBlogProps) {
|
||||
export default function CardBlog({ data, colorPreset = 1, isDescriptionVisible = true }: CardBlogProps) {
|
||||
const linkDetail = `/blog/${data.slug}`;
|
||||
|
||||
if (colorPreset === 2) {
|
||||
return (
|
||||
<div>
|
||||
<article className="post-simple">
|
||||
<div className="h-64 relative">
|
||||
<Link href={linkDetail}>
|
||||
<Image src={data.img?.url ?? ""} alt={data.img?.alt ?? ""} fill />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="post-simple-body">
|
||||
<div className="post-simple-title">
|
||||
<h4>
|
||||
<Link href={linkDetail}>{data.title}</Link>
|
||||
</h4>
|
||||
</div>
|
||||
{isDescriptionVisible && !!data?.description && (
|
||||
<>
|
||||
<div className="post-simple-divider"></div>
|
||||
<div className="post-simple-text">
|
||||
<p>{data.description}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="post-simple-time">
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<span>{data.posted_at}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<article className="post-default">
|
||||
@ -23,10 +58,14 @@ export default function CardBlog({ data }: CardBlogProps) {
|
||||
<Link href={linkDetail}>{data.title}</Link>
|
||||
</h4>
|
||||
</div>
|
||||
{isDescriptionVisible && !!data?.description && (
|
||||
<>
|
||||
<div className="post-default-divider"></div>
|
||||
<div className="post-default-text">
|
||||
<p>{data.description}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="post-default-time">
|
||||
<span className="icon mdi mdi-clock"></span>
|
||||
<span>{data.posted_at}</span>
|
||||
|
@ -36,7 +36,7 @@ export default function ListOfBlog({ searchKeyword }: ListOfBlogProps) {
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
{blogQuery.isFetching && <Loader />}
|
||||
{blogQuery.hasNext && (
|
||||
{!blogQuery.isFetching && blogQuery.hasNext && (
|
||||
<button onClick={fetchMore} className="button button-primary">
|
||||
LOAD MORE...
|
||||
</button>
|
||||
|
48
src/components/blogs/ListOfRecentBlog.tsx
Normal file
48
src/components/blogs/ListOfRecentBlog.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import Loader from "@/components/loaders/Loader";
|
||||
import { useRecentBlogQuery } from "@/services/hooks/blog";
|
||||
import { useEffect } from "react";
|
||||
import CardBlog from "./CardBlog";
|
||||
|
||||
type ListOfRecentBlogProps = {
|
||||
currentBlogId?: number;
|
||||
};
|
||||
|
||||
export default function ListOfRecentBlog({ currentBlogId }: ListOfRecentBlogProps) {
|
||||
const recentBlogQuery = useRecentBlogQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (!!currentBlogId) {
|
||||
recentBlogQuery._fetch({
|
||||
currentBlogId,
|
||||
});
|
||||
}
|
||||
}, [currentBlogId]);
|
||||
|
||||
if (recentBlogQuery.isFetching) {
|
||||
return (
|
||||
<div className="mt-5 w-full">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (recentBlogQuery.data.length <= 0) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="post-simple-group">
|
||||
<div className="post-simple-group-title">
|
||||
<h6>Recent Posts</h6>
|
||||
</div>
|
||||
<div className="post-simple-group-divider"></div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{recentBlogQuery.data.map((blog) => (
|
||||
<CardBlog key={blog.slug} data={blog} colorPreset={2} isDescriptionVisible={false} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -18,8 +18,6 @@ const filename = fileURLToPath(import.meta.url);
|
||||
const dirname = path.dirname(filename);
|
||||
|
||||
export default buildConfig({
|
||||
cors: [process.env.SITE_URL || ""],
|
||||
csrf: [process.env.SITE_URL || ""],
|
||||
admin: {
|
||||
user: Users.slug,
|
||||
importMap: {
|
||||
|
@ -1,7 +1,7 @@
|
||||
export type BlogData = {
|
||||
slug?: string | null;
|
||||
title: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
img?: { url: string; alt?: string };
|
||||
posted_at: string;
|
||||
};
|
||||
|
@ -4,3 +4,7 @@ export type FetchBlogParams = {
|
||||
categoryId?: number;
|
||||
tagId?: number;
|
||||
};
|
||||
|
||||
export type FetchRecentBlogParams = {
|
||||
currentBlogId: number;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BlogData } from "@/schema/blog";
|
||||
import { FetchBlogParams } from "@/schema/services/blog";
|
||||
import { FetchBlogParams, FetchRecentBlogParams } from "@/schema/services/blog";
|
||||
import { useState } from "react";
|
||||
import { fetchBlogREST } from "../rest/blog";
|
||||
import { fetchBlogREST, fetchRecentBlogREST } from "../rest/blog";
|
||||
|
||||
export function useBlogQuery() {
|
||||
const [data, setData] = useState<BlogData[]>([]);
|
||||
@ -14,8 +14,14 @@ export function useBlogQuery() {
|
||||
setFetching(false);
|
||||
|
||||
if (Array.isArray(res?.formattedData)) {
|
||||
if (!!params.page && params.page > 1) {
|
||||
setData((currentData) => {
|
||||
return [...currentData, ...res.formattedData];
|
||||
});
|
||||
} else {
|
||||
setData(res.formattedData);
|
||||
}
|
||||
}
|
||||
setHasNext(res?.hasNextPage ?? false);
|
||||
}
|
||||
|
||||
@ -26,3 +32,24 @@ export function useBlogQuery() {
|
||||
hasNext,
|
||||
};
|
||||
}
|
||||
|
||||
export function useRecentBlogQuery() {
|
||||
const [data, setData] = useState<BlogData[]>([]);
|
||||
const [isFetching, setFetching] = useState(false);
|
||||
|
||||
async function _fetch(params: FetchRecentBlogParams) {
|
||||
setFetching(true);
|
||||
const res = await fetchRecentBlogREST(params);
|
||||
setFetching(false);
|
||||
|
||||
if (Array.isArray(res?.formattedData)) {
|
||||
setData(res.formattedData);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
_fetch,
|
||||
data,
|
||||
isFetching,
|
||||
};
|
||||
}
|
||||
|
@ -37,11 +37,13 @@ export async function fetchBlog({ page, search = "", categoryId, tagId }: FetchB
|
||||
where: queryCondition,
|
||||
});
|
||||
|
||||
const formattedData = blogDataQuery.docs.map((item) => {
|
||||
const formattedData: BlogData[] = blogDataQuery.docs.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
imgFormatted: typeof item.img !== "number" ? { url: item?.img?.url ?? "", alt: item.img.alt } : undefined,
|
||||
createdAtFormatted: formatDate(item.createdAt),
|
||||
slug: item.slug,
|
||||
title: item.title,
|
||||
description: sanitizeBlogContentIntoStringPreview(item.content),
|
||||
img: typeof item.img !== "number" ? { url: item?.img?.url ?? "", alt: item.img.alt } : undefined,
|
||||
posted_at: formatDate(item.createdAt),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Blog } from "@/payload-types";
|
||||
import { BlogData } from "@/schema/blog";
|
||||
import { FetchBlogParams } from "@/schema/services/blog";
|
||||
import { FetchBlogParams, FetchRecentBlogParams } from "@/schema/services/blog";
|
||||
import { formatDate } from "@/utils/datetime";
|
||||
import { sanitizeBlogContentIntoStringPreview } from "@/utils/sanitize";
|
||||
import { PaginatedDocs, Where } from "payload";
|
||||
@ -59,3 +59,43 @@ export async function fetchBlogREST({ page, search = "", categoryId, tagId }: Fe
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRecentBlogREST({ currentBlogId }: FetchRecentBlogParams) {
|
||||
const queryCondition: Where = {
|
||||
_status: { equals: "published" },
|
||||
id: {
|
||||
not_equals: currentBlogId,
|
||||
},
|
||||
};
|
||||
|
||||
const queryParams = stringify(
|
||||
{
|
||||
pagination: true,
|
||||
limit: 2,
|
||||
where: queryCondition,
|
||||
},
|
||||
{ addQueryPrefix: true }
|
||||
);
|
||||
|
||||
const blogRequest = await fetch(`/api/blogs${queryParams}`);
|
||||
|
||||
if (blogRequest.ok) {
|
||||
const resData = (await blogRequest.json()) as PaginatedDocs<Blog>;
|
||||
const formattedData: BlogData[] = resData.docs.map((item) => {
|
||||
return {
|
||||
slug: item.slug,
|
||||
title: item.title,
|
||||
description: sanitizeBlogContentIntoStringPreview(item.content),
|
||||
img: typeof item.img !== "number" ? { url: item?.img?.url ?? "", alt: item.img.alt } : undefined,
|
||||
posted_at: formatDate(item.createdAt),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...resData,
|
||||
formattedData,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user