feat: blog detail fetching and new fixed loader
This commit is contained in:
parent
9a80da754d
commit
9dfa9d2dd8
5
src/app/(main)/blog/[slug]/loading.tsx
Normal file
5
src/app/(main)/blog/[slug]/loading.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import LoaderFixed from "@/components/loaders/LoaderFixed";
|
||||||
|
|
||||||
|
export default function Loading() {
|
||||||
|
return <LoaderFixed />;
|
||||||
|
}
|
@ -1,7 +1,10 @@
|
|||||||
import HeroImage from "@/components/HeroImage";
|
import HeroImage from "@/components/HeroImage";
|
||||||
|
import { fetchBlogDetail } from "@/services/payload/blog";
|
||||||
import { getDefaultMetadata } from "@/utils/metadata";
|
import { getDefaultMetadata } from "@/utils/metadata";
|
||||||
|
import { RichText } from "@payloadcms/richtext-lexical/react";
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
const metaDesc =
|
const metaDesc =
|
||||||
"Explore the latest insights, news, and resources on the Dynamic Realty blog. Read our articles today.";
|
"Explore the latest insights, news, and resources on the Dynamic Realty blog. Read our articles today.";
|
||||||
@ -14,7 +17,12 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlogDetail() {
|
export default async function BlogDetail(props: { params: Promise<{ slug: string }> }) {
|
||||||
|
const params = await props.params;
|
||||||
|
const blog = await fetchBlogDetail(params.slug);
|
||||||
|
|
||||||
|
if (!blog) return notFound();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeroImage />
|
<HeroImage />
|
||||||
@ -25,10 +33,7 @@ export default function BlogDetail() {
|
|||||||
<div className="col-lg-8">
|
<div className="col-lg-8">
|
||||||
<article className="blog-post-solo">
|
<article className="blog-post-solo">
|
||||||
<div className="blog-post-solo-part">
|
<div className="blog-post-solo-part">
|
||||||
<p>
|
<RichText data={blog.data.content} />
|
||||||
Showcasing a warm, traditional-style exterior and the highest caliber of contemporary European
|
|
||||||
finishes throughout, towering glass doors open to grand-scale living spaces.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="blog-post-solo-footer">
|
<div className="blog-post-solo-footer">
|
||||||
@ -36,7 +41,7 @@ export default function BlogDetail() {
|
|||||||
<ul className="blog-post-solo-footer-list">
|
<ul className="blog-post-solo-footer-list">
|
||||||
<li>
|
<li>
|
||||||
<span className="icon mdi mdi-clock"></span>
|
<span className="icon mdi mdi-clock"></span>
|
||||||
<a href="#">February 10, 2021</a>
|
<a href="#">{blog.createdAt}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -116,22 +121,18 @@ export default function BlogDetail() {
|
|||||||
<div className="pdl-xl-40">
|
<div className="pdl-xl-40">
|
||||||
<div className="row row-60">
|
<div className="row row-60">
|
||||||
<div className="col-md-6 col-lg-12">
|
<div className="col-md-6 col-lg-12">
|
||||||
<form className="form-lg rd-search rd-search-classic">
|
<form action="/blog" className="form-lg rd-search rd-search-classic">
|
||||||
<div className="form-wrap">
|
<div className="form-wrap">
|
||||||
<label className="form-label" htmlFor="rd-search-form-input">
|
|
||||||
Search the blog...
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form-input"
|
className="form-input"
|
||||||
id="rd-search-form-input"
|
id="rd-search-form-input"
|
||||||
type="text"
|
type="text"
|
||||||
name="s"
|
name="s"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
placeholder="Search blog..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className="rd-search-submit" type="submit">
|
<button className="rd-search-submit" type="submit"></button>
|
||||||
{" "}
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-6 col-lg-12">
|
<div className="col-md-6 col-lg-12">
|
||||||
|
@ -14,8 +14,8 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Blog({ searchParams }: { searchParams?: Promise<{ s?: string }> }) {
|
export default async function Blog(props: { searchParams?: Promise<{ s?: string }> }) {
|
||||||
const params = await searchParams;
|
const searchParams = await props?.searchParams;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -32,14 +32,14 @@ export default async function Blog({ searchParams }: { searchParams?: Promise<{
|
|||||||
type="text"
|
type="text"
|
||||||
name="s"
|
name="s"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
defaultValue={params?.s}
|
defaultValue={searchParams?.s}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className="rd-search-submit" type="submit"></button>
|
<button className="rd-search-submit" type="submit"></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ListOfBlog searchKeyword={params?.s} />
|
<ListOfBlog searchKeyword={searchParams?.s} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
--color-colorContactForm: var(--color-colorExt50);
|
--color-colorContactForm: var(--color-colorExt50);
|
||||||
--color-colorText1: var(--color-colorExt10);
|
--color-colorText1: var(--color-colorExt10);
|
||||||
--color-colorText2: var(--color-colorExt20);
|
--color-colorText2: var(--color-colorExt20);
|
||||||
|
--color-colorLoaderBackground: var(--color-colorExt20);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
@ -7,11 +7,13 @@ type CardBlogProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function CardBlog({ data }: CardBlogProps) {
|
export default function CardBlog({ data }: CardBlogProps) {
|
||||||
|
const linkDetail = `/blog/${data.slug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<article className="post-default">
|
<article className="post-default">
|
||||||
<div className="h-64 relative">
|
<div className="h-64 relative">
|
||||||
<Link href="#">
|
<Link href={linkDetail}>
|
||||||
<Image src={data.img?.url ?? ""} alt={data.img?.alt ?? ""} fill />
|
<Image src={data.img?.url ?? ""} alt={data.img?.alt ?? ""} fill />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@ -27,7 +29,7 @@ export default function CardBlog({ data }: CardBlogProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="post-default-time">
|
<div className="post-default-time">
|
||||||
<span className="icon mdi mdi-clock"></span>
|
<span className="icon mdi mdi-clock"></span>
|
||||||
<a href="#">{data.posted_at}</a>
|
<Link href={linkDetail}>{data.posted_at}</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Loader from "@/components/Loader";
|
import Loader from "@/components/loaders/Loader";
|
||||||
import { useBlogQuery } from "@/services/hooks/blog";
|
import { useBlogQuery } from "@/services/hooks/blog";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import CardBlog from "./CardBlog";
|
import CardBlog from "./CardBlog";
|
||||||
|
9
src/components/loaders/LoaderFixed.tsx
Normal file
9
src/components/loaders/LoaderFixed.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import Loader from "./Loader";
|
||||||
|
|
||||||
|
export default function LoaderFixed() {
|
||||||
|
return (
|
||||||
|
<div className="fixed top-0 left-0 flex justify-center items-center w-screen h-screen bg-colorLoaderBackground z-50">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
import payloadConfig from "@/payload.config";
|
import payloadConfig from "@/payload.config";
|
||||||
|
import { BlogData } from "@/schema/blog";
|
||||||
import { FetchBlogParams } from "@/schema/services/blog";
|
import { FetchBlogParams } from "@/schema/services/blog";
|
||||||
import { formatDate } from "@/utils/datetime";
|
import { formatDate } from "@/utils/datetime";
|
||||||
import { getRandomNumber } from "@/utils/general";
|
import { getRandomNumber } from "@/utils/general";
|
||||||
|
import { sanitizeBlogContentIntoStringPreview } from "@/utils/sanitize";
|
||||||
import { getPayload, Where } from "payload";
|
import { getPayload, Where } from "payload";
|
||||||
|
|
||||||
export async function fetchBlog({ page, search = "", categoryId, tagId }: FetchBlogParams = {}) {
|
export async function fetchBlog({ page, search = "", categoryId, tagId }: FetchBlogParams = {}) {
|
||||||
@ -71,11 +73,13 @@ export async function fetchBlogSuggestion() {
|
|||||||
limit: limitPerPage,
|
limit: limitPerPage,
|
||||||
});
|
});
|
||||||
|
|
||||||
const formattedData = blogDataQuery.docs.map((item) => {
|
const formattedData: BlogData[] = blogDataQuery.docs.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
slug: item.slug,
|
||||||
imgFormatted: typeof item.img !== "number" ? { url: item?.img?.url ?? "", alt: item.img.alt } : undefined,
|
title: item.title,
|
||||||
createdAtFormatted: formatDate(item.createdAt),
|
description: sanitizeBlogContentIntoStringPreview(item.content),
|
||||||
|
img: typeof item.img !== "number" ? { url: item?.img?.url ?? "", alt: item.img.alt } : undefined,
|
||||||
|
posted_at: formatDate(item.createdAt),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export function formatDate(iso: string, format: string = "MMM, D YYYY") {
|
export function formatDate(iso: string, format: string = "MMMM DD, YYYY") {
|
||||||
return dayjs(iso).format(format);
|
return dayjs(iso).format(format);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user