Commit 45a48db0 by Madhankumar

blog app changes

parent a3c4ae7a
"use client"; "use client";
import Header from "@components/base/header"; import Header from "@components/base/header";
import { useAppContext } from "@context/index"; import { useAppContext } from "@context/index";
import { useRouter } from "next/navigation";
export default function PageHeader() { export default function PageHeader() {
const { theme, toggleTheme, setInput } = useAppContext(); const { theme, toggleTheme } = useAppContext();
const router = useRouter();
let currentPage = 1;
const handleSearch = (value) => { const handleSearch = (value) => {
setInput(value); if (value) {
router.push(`/?page=${currentPage}&search=${value}`, undefined, {
shallow: true,
});
} else {
router.push(`/?page=${currentPage}`, undefined, { shallow: true });
}
}; };
const handleClose = () => { const handleClose = () => {
setInput(""); router.push(`/?page=${currentPage}`, undefined, { shallow: true });
}; };
return ( return (
......
import { ThemeProvider } from "@context/index";
import "@styles/global.css";
export default function App({ Component, pageProps }) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}
// api/search/index.js
import { getPosts } from "@lib/api";
import { NextResponse } from "next/server";
export async function GET(request) {
const { searchParams } = new URL(request.url);
const page = searchParams.get("page");
const search = searchParams.get("search");
const { blogs } = await getPosts("posts", 1);
const filterBlog = blogs.filter((e) => {
return e.title.toLowerCase().includes(search.toLowerCase());
});
let pageNo = filterBlog.length > 6 ? page : 1;
const itemsPerPage = 6;
// Calculate the start and end indices for the current page
const startIndex = (pageNo - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const slicedData = Array.isArray(filterBlog)
? filterBlog.slice(startIndex, endIndex)
: [];
const response = { data: slicedData, total: filterBlog.length };
return NextResponse.json(response);
}
import { getAllPosts } from "@lib/posts";
import { NextResponse } from "next/server";
export async function GET() {
const blogs = await getAllPosts("posts");
return NextResponse.json(blogs);
}
"use client";
import { useEffect, useState } from "react";
import BlogLists from "@components/top-level/blog-lists";
import Loading from "./loading";
import { useAppContext } from "@context/index";
import styles from "./container.module.css";
export default function BlogContainer({ blogs }) {
const { input } = useAppContext();
const [filteredBlogs, setFilteredBlogs] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
handleBlogs();
}, [input, blogs]); // Added 'blogs' as a dependency
const handleBlogs = () => {
setLoading(true); // Set loading state to true when starting to filter
const filtered = blogs.filter((blog) => {
const lowerCaseBlogTitle = blog.title.toLowerCase();
const lowerCaseInput = input.toLowerCase();
return lowerCaseBlogTitle.includes(lowerCaseInput);
});
setFilteredBlogs(filtered);
setLoading(false); // Set loading state to false when filtering is complete
};
return (
<>
{loading ? (
<Loading />
) : filteredBlogs.length ? (
<BlogLists
title="Lifestyle"
description="Lorem ipsum dolor sit amet elit. Id quaerat amet ipsum sunt et quos dolorum."
blogs={filteredBlogs}
/>
) : (
<h2 className={styles.center}>No data found</h2>
)}
</>
);
}
.center {
text-align: center;
}
"use client";
import { useState, useEffect } from "react";
import { ThemeProvider } from "@context/index"; import { ThemeProvider } from "@context/index";
import "./globals.css"; import "@styles/globals.css";
export default function RootLayout({ children, header }) { export default function RootLayout({ children, header }) {
// Check if localStorage is available (client-side) // Check if localStorage is available (client-side)
const themeClass = const themeClass =
......
import styles from "./loading.module.css"; import LoadingComponent from "@components/base/loading";
const Loading = () => { const Loading = () => {
return <h2 className={styles.loading}>Loading ...</h2>; return <LoadingComponent />;
}; };
export default Loading; export default Loading;
.loading {
display: flex;
align-items: center;
justify-content: center;
}
import styles from "./not-found.module.css"; import PageNotFoundComponent from "@components/base/pagenotfound";
const PageNotFound = () => { const PageNotFound = () => {
return <h1 className={styles.notfound}>404 Not Found</h1>; return <PageNotFoundComponent />;
}; };
export default PageNotFound; export default PageNotFound;
.notfound {
display: grid;
place-content: center;
}
import BlogContainer from "./container"; import BlogLists from "@components/top-level/blog-lists";
import { getAllPosts } from "@lib/posts"; import { getPosts } from "@lib/api";
import styles from "./page.module.css"; import styles from "./page.module.css";
import Loading from "./loading";
const Home = () => { async function fetchBlogsByTitle(pageNo, search) {
let blogs; try {
const fetchData = () => { const searchResponse = await fetch(
try { `http://localhost:3000/api/blogs/?page=${pageNo}&search=${search}`
blogs = getAllPosts("posts"); // {
} catch (error) { // next: {
console.error("Error fetching blogs:", error); // revalidate: 100, //after 100sec
} // },
}; // }
);
return await searchResponse.json();
} catch (err) {
console.log(er.message);
}
}
fetchData(); async function fetchBlogs(pageNo) {
try {
const response = await getPosts("posts", pageNo);
return response;
} catch (err) {
console.log(err.message);
}
}
const Home = async ({ searchParams }) => {
let blogs = [];
let blogsLength = 0;
let pageNo = searchParams.page || 1;
let search = searchParams.search || "";
let response;
if (!search) {
const { data, total } = await fetchBlogs(pageNo);
response = { data, total: total };
} else {
response = await fetchBlogsByTitle(pageNo, search);
}
blogs = response.data;
blogsLength = response.total;
pageNo = blogsLength > 6 ? pageNo : 1;
return ( return (
<div className={styles.container}> <div className={styles.container}>
{blogs.length ? <BlogContainer blogs={blogs} /> : <Loading />} {blogs?.length ? (
<BlogLists
title="Lifestyle"
description="Lorem ipsum dolor sit amet elit. Id quaerat amet ipsum sunt et quos dolorum."
blogs={blogs}
totalBlogLength={Number(blogsLength)}
currentPage={Number(pageNo)}
/>
) : (
<h2 className="center">No data found</h2>
)}
</div> </div>
); );
}; };
......
.container { .container {
padding-top: 3rem; padding-top: 1rem;
} }
import SingleBlog from "@components/top-level/single-blog"; import SingleBlog from "@components/top-level/single-blog";
import { getPostById } from "@lib/posts"; import Loading from "app/loading";
import { getPostById } from "@lib/api";
const SingleBlogApp = ({ params }) => { const fetchBlogById = async (id) => {
let blog; try {
const fetchBlogsBySlug = () => { const blogPost = await getPostById("posts", id);
try { return blogPost[0];
const blogPost = getPostById("posts", params.id); } catch (error) {
blog = blogPost[0]; console.error("Error fetching blog:", error.message);
} catch (err) { return null;
console.log("error", err.message); }
} };
};
const SingleBlogApp = async ({ params: { id } }) => {
const blog = await fetchBlogById(id);
fetchBlogsBySlug(); return <div>{blog ? <SingleBlog {...blog} /> : <Loading />}</div>;
return (
<SingleBlog
title={blog.title}
publishedDate={blog.publishedDate}
author={blog.author}
category={blog.category}
blogImage={blog.blogImage}
readingTime={blog.readingTime}
categories={blog.categories}
markdown={blog.markdown}
/>
);
}; };
export default SingleBlogApp; export default SingleBlogApp;
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import cn from "classnames"; import cn from "classnames";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
const Button = ({ children, isDisabled, variant, className, ...props }) => { const Button = ({ children, isDisabled, variant, className, ...props }) => {
const classNames = cn(styles["btn"], { const classNames = cn(styles["btn"], {
[styles[variant]]: variant, [styles[variant]]: variant,
[styles[className]]: className, [styles[className]]: className,
[styles.disabled]: isDisabled === true, [styles.disabled]: isDisabled,
}); });
return ( return (
<button className={`${classNames}`} {...props} disabled={isDisabled}> <button className={classNames} disabled={isDisabled} {...props}>
{children} {children}
</button> </button>
); );
......
...@@ -19,7 +19,7 @@ function Header({ name, currentTheme, onSearch, onClose, onThemeChange }) { ...@@ -19,7 +19,7 @@ function Header({ name, currentTheme, onSearch, onClose, onThemeChange }) {
<header className={styles.header}> <header className={styles.header}>
<div className={styles.container}> <div className={styles.container}>
<nav className={styles.navbar}> <nav className={styles.navbar}>
<Link className={styles["navbar-brand"]} href="/"> <Link className={styles["navbar-brand"]} href={`/?page=1`}>
<Icons name="newspaper" size="medium" classes="newspaper-icon" /> <Icons name="newspaper" size="medium" classes="newspaper-icon" />
<h3>{name}</h3> <h3>{name}</h3>
</Link> </Link>
...@@ -28,7 +28,7 @@ function Header({ name, currentTheme, onSearch, onClose, onThemeChange }) { ...@@ -28,7 +28,7 @@ function Header({ name, currentTheme, onSearch, onClose, onThemeChange }) {
<Search onSearch={handleSearch} onClose={handleClose} /> <Search onSearch={handleSearch} onClose={handleClose} />
</div> </div>
<div onClick={handleTheme} className={styles.themelight}> <div onClick={handleTheme} className={styles.themeLight}>
<Icons <Icons
name={currentTheme === "light" ? "sun" : "moon"} name={currentTheme === "light" ? "sun" : "moon"}
size="medium" size="medium"
......
.header { .header {
position: sticky; position: sticky;
top: 57px; top: 0;
z-index: 1000; z-index: 1000;
margin-bottom: 40px; margin-bottom: 40px;
} }
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0.7em 10%; padding: 0.7em 12%;
} }
.header .container .navbar .navbar-brand { .header .container .navbar .navbar-brand {
display: grid; display: grid;
...@@ -24,10 +24,10 @@ ...@@ -24,10 +24,10 @@
.header .container .navbar .navbar-brand h2 { .header .container .navbar .navbar-brand h2 {
color: var(--title-color); color: var(--title-color);
} }
.header .container .navbar .navbar-brand .themelight { .themeLight {
display: flex; cursor: pointer;
gap: 1rem;
} }
.search { .search {
display: none; display: none;
} }
......
const LoadingComponent = () => {
return <h2 className="center">Loading ...</h2>;
};
export default LoadingComponent;
import Link from "next/link";
const PageNotFoundComponent = () => {
return (
<div className="center">
<div>
<h1>404 Page Not Found</h1>
<p style={{ textAlign: "center" }}>
Please redirect to <Link href="/">Home</Link>
</p>
</div>
</div>
);
};
export default PageNotFoundComponent;
"use client"; import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import { useDebouncedCallback } from "use-debounce";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useSearchParams } from "next/navigation";
import Icons from "@components/base/icons"; import Icons from "@components/base/icons";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
const Search = ({ onSearch, onClose }) => { const Search = ({ onSearch, onClose }) => {
const [text, setText] = useState(""); const searchParams = useSearchParams();
const search = searchParams.get("search") || "";
const handleDebounceChange = useDebouncedCallback((value) => { const [inputValue, setInputValue] = useState(search);
onSearch(value);
}, 1000); useEffect(() => {
const handleInputChange = (value) => { setInputValue(search);
setText(value); }, [search]);
handleDebounceChange(value);
};
const handleCustomClear = (e) => { const handleCustomClear = () => {
e.preventDefault();
onClose(""); onClose("");
setText(""); setInputValue(""); // Reset the input value
};
const handleKeyPress = (e) => {
if (e.key === "Enter") {
onSearch(inputValue);
}
};
const handleChange = (e) => {
setInputValue(e);
}; };
return ( return (
...@@ -31,11 +37,12 @@ const Search = ({ onSearch, onClose }) => { ...@@ -31,11 +37,12 @@ const Search = ({ onSearch, onClose }) => {
className={styles["search-input"]} className={styles["search-input"]}
type="text" type="text"
autoFocus={true} autoFocus={true}
value={text} value={inputValue}
onChange={(e) => handleInputChange(e.target.value)} onChange={(e) => handleChange(e.target.value)}
onKeyUp={handleKeyPress}
placeholder="Discover news, articles and more" placeholder="Discover news, articles and more"
/> />
{text && ( {inputValue && (
<span className={`${styles["close-icon"]}`} onClick={handleCustomClear}> <span className={`${styles["close-icon"]}`} onClick={handleCustomClear}>
<Icons name="close" size="small" /> <Icons name="close" size="small" />
</span> </span>
...@@ -48,4 +55,5 @@ Search.propTypes = { ...@@ -48,4 +55,5 @@ Search.propTypes = {
onSearch: PropTypes.func, onSearch: PropTypes.func,
onClose: PropTypes.func, onClose: PropTypes.func,
}; };
export default Search; export default Search;
"use client"; "use client";
import React, { useEffect, useState } from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Card from "@components/base/card"; import Card from "@components/base/card";
import Pagination from "@components/top-level/pagination"; import Pagination from "@components/top-level/pagination";
import { useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
const BlogLists = ({ title, description, blogs }) => { const BlogLists = ({
title,
description,
blogs,
currentPage,
totalBlogLength,
}) => {
const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
let searchPage = searchParams.get("page");
const length = blogs.length;
const itemsPerPage = 6;
const [currentPage, setCurrentPage] = useState(searchPage);
// Calculate the start and end indices for the current page
const startIndex = blogs.length > 6 ? (currentPage - 1) * itemsPerPage : 0;
const endIndex = startIndex + itemsPerPage;
const slicedData = blogs.slice(startIndex, endIndex);
useEffect(() => { const redirect = (pageNumber) => {
setCurrentPage(parseInt(searchPage) || 1); const searchQuery = searchParams.get("search")
}, [searchPage]); ? `&search=${searchParams.get("search")}`
// Handle page change : "";
const newUrl = `?page=${pageNumber}${searchQuery}`;
return router.push(newUrl, undefined, { shallow: true });
};
const handlePageChange = (pageNumber) => { const handlePageChange = (pageNumber) => {
setCurrentPage(pageNumber); redirect(pageNumber);
window.scrollTo({ top: 0, behavior: "smooth" }); window.scrollTo({ top: 0, behavior: "smooth" });
}; };
...@@ -31,7 +34,7 @@ const BlogLists = ({ title, description, blogs }) => { ...@@ -31,7 +34,7 @@ const BlogLists = ({ title, description, blogs }) => {
<h2 className={styles.title}>{title}</h2> <h2 className={styles.title}>{title}</h2>
<p className={styles.description}>{description}</p> <p className={styles.description}>{description}</p>
<div className={styles.row}> <div className={styles.row}>
{slicedData.map((blogData, i) => ( {blogs.map((blogData, i) => (
<div key={i} className={styles.col}> <div key={i} className={styles.col}>
<Card {...blogData} /> <Card {...blogData} />
</div> </div>
...@@ -39,9 +42,9 @@ const BlogLists = ({ title, description, blogs }) => { ...@@ -39,9 +42,9 @@ const BlogLists = ({ title, description, blogs }) => {
</div> </div>
<div className={styles.pagination}> <div className={styles.pagination}>
<Pagination <Pagination
currentPage={currentPage} currentPage={Number(currentPage)}
total={length} total={Number(totalBlogLength)}
perPage={itemsPerPage} perPage={6}
onPageChange={handlePageChange} onPageChange={handlePageChange}
/> />
</div> </div>
...@@ -53,6 +56,8 @@ BlogLists.propTypes = { ...@@ -53,6 +56,8 @@ BlogLists.propTypes = {
title: PropTypes.string, title: PropTypes.string,
description: PropTypes.string, description: PropTypes.string,
blogs: PropTypes.array, blogs: PropTypes.array,
currentPage: PropTypes.number,
totalBlogLength: PropTypes.number,
}; };
export default BlogLists; export default BlogLists;
import { useState, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Button from "@components/base/button"; import Button from "@components/base/button";
import { useRouter } from "next/navigation";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
function Pagination({ total, currentPage, onPageChange, perPage }) { function Pagination({ total, currentPage, onPageChange, perPage }) {
const router = useRouter();
//Set number of pages
const numberOfPages = [];
const totalPages = Math.ceil(total / perPage); const totalPages = Math.ceil(total / perPage);
const totalPage = totalPages <= 1 ? 1 : totalPages; const totalPage = totalPages <= 1 ? 1 : totalPages;
const numberOfPages = [];
for (let i = 1; i <= totalPage; i++) { for (let i = 1; i <= totalPage; i++) {
numberOfPages.push(i); numberOfPages.push(i);
} }
// Current active button number
const [currentButton, setCurrentButton] = useState(1);
// Array of buttons what we see on the page
const [arrOfCurrButtons, setArrOfCurrButtons] = useState([]);
useEffect(() => {
let tempNumberOfPages = [...arrOfCurrButtons];
//adding dots
let dotsInitial = "...";
let dotsLeft = "...";
let dotsRight = "...";
if (numberOfPages.length < 6) {
tempNumberOfPages = numberOfPages;
} else if (currentButton >= 1 && currentButton <= 3) {
tempNumberOfPages = [1, 2, 3, 4, dotsInitial, numberOfPages.length];
} else if (currentButton === 4) {
const sliced = numberOfPages.slice(0, 5);
tempNumberOfPages = [...sliced, dotsInitial, numberOfPages.length];
} else if (currentButton > 4 && currentButton < numberOfPages.length - 2) {
// from 5 to 8 -> (10 - 2)
const sliced1 = numberOfPages.slice(currentButton - 2, currentButton); // sliced1 (5-2, 5) -> [4,5]
const sliced2 = numberOfPages.slice(currentButton, currentButton + 1); // sliced1 (5, 5+1) -> [6]
tempNumberOfPages = [
1,
dotsLeft,
...sliced1,
...sliced2,
dotsRight,
numberOfPages.length,
];
} else if (currentButton > numberOfPages.length - 3) {
// > 7
const sliced = numberOfPages.slice(numberOfPages.length - 4); // slice(10-4)
tempNumberOfPages = [1, dotsLeft, ...sliced];
}
setArrOfCurrButtons(tempNumberOfPages);
}, [total, currentButton, perPage]);
//update currentButton value if currentPageNo changes
useEffect(() => {
setCurrentButton(currentPage);
router.push(`/?page=${currentPage}`);
}, [currentPage]);
//onClick function
const handleCurrentPage = (item) => { const handleCurrentPage = (item) => {
setCurrentButton(item);
onPageChange(item); onPageChange(item);
}; };
// Logic for displaying buttons
let tempNumberOfPages = [];
let dotsLeft = "...";
let dotsRight = "...";
if (numberOfPages.length < 6) {
tempNumberOfPages = numberOfPages;
} else if (currentPage >= 1 && currentPage <= 3) {
tempNumberOfPages = [1, 2, 3, 4, dotsRight, numberOfPages.length];
} else if (currentPage === 4) {
const sliced = numberOfPages.slice(0, 5);
tempNumberOfPages = [...sliced, dotsRight, numberOfPages.length];
} else if (currentPage > 4 && currentPage < numberOfPages.length - 2) {
const sliced1 = numberOfPages.slice(currentPage - 2, currentPage);
const sliced2 = numberOfPages.slice(currentPage, currentPage + 1);
tempNumberOfPages = [
1,
dotsLeft,
...sliced1,
...sliced2,
dotsRight,
numberOfPages.length,
];
} else if (currentPage > numberOfPages.length - 3) {
const sliced = numberOfPages.slice(numberOfPages.length - 4);
tempNumberOfPages = [1, dotsLeft, ...sliced];
}
return ( return (
<div className={styles["pagination-container"]}> <div className={styles["pagination-container"]}>
<span className={styles.previous}> <span className={styles.previous}>
<Button <Button
variant="secondary" variant="secondary"
isDisabled={currentButton <= 1 ? true : false} isDisabled={currentPage <= 1}
onClick={() => onClick={() => handleCurrentPage(currentPage - 1)}
currentButton <= 1 ? "" : handleCurrentPage(currentButton - 1)
}
> >
&laquo; Prev &laquo; Prev
</Button> </Button>
</span> </span>
{arrOfCurrButtons.map((item, index) => { {tempNumberOfPages.map((item, index) => (
return ( <span key={index}>
<span key={index}> <Button
<Button isDisabled={totalPage === 1}
variant={currentButton === item ? "primary" : "secondary"} variant={currentPage === item ? "primary" : "secondary"}
primary={currentButton === item ? true : false} onClick={() => (item === "..." ? "" : handleCurrentPage(item))}
onClick={() => (item === "..." ? "" : handleCurrentPage(item))} >
> {item}
{item} </Button>
</Button> </span>
</span> ))}
);
})}
<span className={styles.next}> <span className={styles.next}>
<Button <Button
variant="secondary" variant="secondary"
isDisabled={currentButton === numberOfPages.length ? true : false} isDisabled={currentPage === totalPage}
onClick={() => onClick={() => handleCurrentPage(currentPage + 1)}
currentButton === numberOfPages.length
? ""
: handleCurrentPage(currentButton + 1)
}
> >
Next &raquo; Next &raquo;
</Button> </Button>
...@@ -115,4 +83,5 @@ Pagination.propTypes = { ...@@ -115,4 +83,5 @@ Pagination.propTypes = {
currentPage: PropTypes.number, currentPage: PropTypes.number,
onPageChange: PropTypes.func, onPageChange: PropTypes.func,
}; };
export default Pagination; export default Pagination;
...@@ -3,6 +3,7 @@ import Image from "next/image"; ...@@ -3,6 +3,7 @@ import Image from "next/image";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw"; import rehypeRaw from "rehype-raw";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
function SingleBlog({ function SingleBlog({
title, title,
publishedDate, publishedDate,
...@@ -14,38 +15,38 @@ function SingleBlog({ ...@@ -14,38 +15,38 @@ function SingleBlog({
markdown, markdown,
}) { }) {
return ( return (
<div className={styles["container"]}> <div className={styles.container}>
<div className={styles.headtitle}> <div className={styles.headtitle}>
<h1 className={styles.title}>{title}</h1> <h1 className={styles.title}>{title}</h1>
</div> </div>
<div className={styles["blog-list"]}> <div className={styles.blogList}>
<ul> <ul>
<li> <li>
Posted on&nbsp; Posted on&nbsp;
<span className={styles["list-bold"]}>{publishedDate}</span> <span className={styles.listBold}>{publishedDate}</span>
</li> </li>
<li> <li>
By&nbsp; By&nbsp;
<a href="#" className={styles["list-bold"]}> <a href="#" className={styles.listBold}>
{author.name} {author.name}
</a> </a>
</li> </li>
<li> <li>
Published in&nbsp; Published in&nbsp;
<a href="#" className={styles["list-bold"]}> <a href="#" className={styles.listBold}>
{category} {category}
</a> </a>
</li> </li>
<li> <li>
<a href="#" className={styles["list-bold"]}> <a href="#" className={styles.listBold}>
{readingTime} {readingTime} read
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<div className={styles["blog-container"]}> <div className={styles.blogContainer}>
<div className={styles.blogimage}> <div className={styles.blogImage}>
<Image <Image
className={styles.img} className={styles.img}
src={blogImage.url} src={blogImage.url}
...@@ -54,12 +55,12 @@ function SingleBlog({ ...@@ -54,12 +55,12 @@ function SingleBlog({
height={blogImage.height} height={blogImage.height}
/> />
</div> </div>
<div className={styles["blog-content"]}> <div className={styles.blogContent}>
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{markdown}</ReactMarkdown> <ReactMarkdown rehypePlugins={[rehypeRaw]}>{markdown}</ReactMarkdown>
<div className={styles.tag}> <div className={styles.tag}>
<span>Tags:</span> <span>Tags:</span>
{categories.map((e, i) => ( {categories.map((category, index) => (
<p key={i}>{e}</p> <p key={index}>{category}</p>
))} ))}
</div> </div>
</div> </div>
...@@ -84,4 +85,5 @@ SingleBlog.propTypes = { ...@@ -84,4 +85,5 @@ SingleBlog.propTypes = {
categories: PropTypes.array, categories: PropTypes.array,
markdown: PropTypes.any, markdown: PropTypes.any,
}; };
export default SingleBlog; export default SingleBlog;
...@@ -6,19 +6,20 @@ ...@@ -6,19 +6,20 @@
.blog-list ul { .blog-list ul {
padding: 0rem 2.3rem; padding: 0rem 2.3rem;
} }
.blog-content { .blogContent {
padding: 1rem; padding: 1rem;
border-radius: 15px; border-radius: 15px;
margin-top: 2rem; margin-top: 2rem;
font-size: 18px; font-size: 18px;
line-height: 1.2; line-height: 1.2;
color: var(--font-color-300);
background: var(--card-bg); background: var(--card-bg);
} }
.blog-content p, .blogContent p,
.blog-content ol li { .blogContent ol li {
font-size: clamp(1rem, 0.8rem + 1vw, 1.2rem); font-size: clamp(1rem, 0.8rem + 1vw, 1.2rem);
} }
.blogimage { .blogImage {
overflow: hidden; overflow: hidden;
border-radius: 15px; border-radius: 15px;
width: 100%; width: 100%;
...@@ -30,25 +31,25 @@ ...@@ -30,25 +31,25 @@
object-fit: cover; object-fit: cover;
display: block; display: block;
} }
.blog-content blockquote p { .blogContent blockquote p {
font-size: 20px; font-size: 20px;
} }
.blog-list ul { .blogList ul {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
list-style: none; list-style: none;
gap: 1em; gap: 1em;
color: var(--font-color-300); color: var(--font-color-300);
} }
.blog-list ul li { .blogList ul li {
font-size: clamp(1rem, 0.4rem + 0.5vw, 1.1rem); font-size: clamp(1rem, 0.4rem + 0.5vw, 1.1rem);
line-height: 1; line-height: 1;
} }
.blog-list ul li a { .blogList ul li a {
text-decoration: none; text-decoration: none;
color: var(--font-color-300); color: var(--font-color-300);
} }
.blog-container { .blogContainer {
width: 90%; width: 90%;
margin: 0 auto; margin: 0 auto;
} }
...@@ -58,7 +59,7 @@ ...@@ -58,7 +59,7 @@
gap: 0.2rem; gap: 0.2rem;
} }
.tag > p { .tag > p {
background: #e2e8f0; background: var(--bg-grey);
padding: 0.3em 1em; padding: 0.3em 1em;
border-radius: 40px; border-radius: 40px;
color: #718096; color: #718096;
...@@ -73,12 +74,12 @@ ...@@ -73,12 +74,12 @@
background: #5a67d8; background: #5a67d8;
color: white; color: white;
} }
.blog-content blockquote { .blogContent blockquote {
padding: 0.66001rem 1rem 1rem; padding: 0.66001rem 1rem 1rem;
border-left: 3px solid #5a67d8; border-left: 3px solid #5a67d8;
margin: 0; margin: 0;
} }
.blog-content blockquote p::before { .blogContent blockquote p::before {
content: "\2014\00A0"; content: "\2014\00A0";
} }
.head-container { .head-container {
...@@ -90,17 +91,17 @@ ...@@ -90,17 +91,17 @@
font-weight: 600; font-weight: 600;
color: var(--font-color-900); color: var(--font-color-900);
} }
.list-bold { .listBold {
font-weight: bold; font-weight: bold;
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.headtitle, .headtitle,
.blog-list ul { .blogList ul {
width: min(80rem, 90%); width: min(80rem, 90%);
padding: 0rem 3.5rem; padding: 0rem 3.5rem;
} }
.blog-content { .blogContent {
padding: 3rem; padding: 3rem;
} }
.tag { .tag {
...@@ -112,11 +113,11 @@ ...@@ -112,11 +113,11 @@
padding-inline: 40px; padding-inline: 40px;
} }
.headtitle, .headtitle,
.blog-list ul { .blogList ul {
width: min(70rem, 90%); width: min(70rem, 90%);
margin: 0 auto; margin: 0 auto;
} }
.blog-list ul { .blogList ul {
margin-top: 1rem; margin-top: 1rem;
} }
} }
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
"@img/*": ["public/images/*"], "@img/*": ["public/images/*"],
"@posts/*": ["posts/*"], "@posts/*": ["posts/*"],
"@context/*": ["context/*"], "@context/*": ["context/*"],
"@lib/*": ["lib/*"] "@lib/*": ["lib/*"],
"@styles/*": ["styles/*"]
}, },
"experimentalDecorators": true "experimentalDecorators": true
} }
......
import fs from "fs"; import fs from "fs/promises";
import path from "path"; import path from "path";
import matter from "gray-matter"; import matter from "gray-matter";
import { remark } from "remark"; import { remark } from "remark";
import html from "remark-html"; import html from "remark-html";
export function getPostById(fileDirectory, slug) { export async function getPostById(fileDirectory, slug) {
const directory = path.join(process.cwd(), fileDirectory); const directory = path.join(process.cwd(), fileDirectory);
const fileNames = fs.readdirSync(directory); const fileNames = await fs.readdir(directory);
const data = fileNames const data = await Promise.all(
.map((fileName) => { fileNames.map(async (fileName) => {
const fullPath = path.join(fileDirectory, fileName); const fullPath = path.join(fileDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, "utf8"); const fileContents = await fs.readFile(fullPath, "utf8");
const { data, content } = matter(fileContents); const { data, content } = matter(fileContents);
const processedContent = remark() const processedContent = remark()
.use(html) .use(html)
...@@ -22,23 +22,34 @@ export function getPostById(fileDirectory, slug) { ...@@ -22,23 +22,34 @@ export function getPostById(fileDirectory, slug) {
...data, ...data,
}; };
}) })
.filter((e) => e.id == slug); );
return data; return data.filter((e) => e.id == slug);
} }
export function getAllPosts(fileDirectory) {
export async function getPosts(fileDirectory, page) {
const directory = path.join(process.cwd(), fileDirectory); const directory = path.join(process.cwd(), fileDirectory);
const fileNames = fs.readdirSync(directory); const fileNames = await fs.readdir(directory);
const data = fileNames.map((fileName) => {
const dataPromises = fileNames.map(async (fileName) => {
const id = fileName.replace(/\.md$/, ""); const id = fileName.replace(/\.md$/, "");
const fullPath = path.join(fileDirectory, fileName); const fullPath = path.join(fileDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, "utf8"); const fileContents = await fs.readFile(fullPath, "utf8");
const matterResult = matter(fileContents); const { data, content } = matter(fileContents);
return { return {
id, id,
...matterResult.data, ...data,
}; };
}); });
return data;
const data = await Promise.all(dataPromises);
const itemsPerPage = 6;
// Calculate the start and end indices for the current page
const startIndex = (page - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const slicedData = Array.isArray(data)
? data.slice(startIndex, endIndex)
: [];
const response = { data: slicedData, total: data?.length, blogs: data };
return response;
} }
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
--secondary-bgcolor: #131617; --secondary-bgcolor: #131617;
--primary-color: #fff; --primary-color: #fff;
--secondary-color: #fff; --secondary-color: #fff;
--bg-grey: #2a2a2a;
} }
body, body,
...@@ -107,8 +108,9 @@ p { ...@@ -107,8 +108,9 @@ p {
display: flex; display: flex;
gap: 1em; gap: 1em;
} }
/* @media (prefers-color-scheme: dark) {
html { .center {
background-color: var(--bg-color); display: flex;
} align-items: center;
} */ justify-content: center;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment