diff --git a/src/common/components/elements/EmptyState.tsx b/src/common/components/elements/EmptyState.tsx index dae54d3e..7e99903d 100644 --- a/src/common/components/elements/EmptyState.tsx +++ b/src/common/components/elements/EmptyState.tsx @@ -6,7 +6,7 @@ type EmptyStatePageProps = { const EmptyState = ({ message }: EmptyStatePageProps) => { return ( -
+

{message}

diff --git a/src/common/components/elements/Pagination.tsx b/src/common/components/elements/Pagination.tsx index 209dfc41..69d0b722 100644 --- a/src/common/components/elements/Pagination.tsx +++ b/src/common/components/elements/Pagination.tsx @@ -53,6 +53,10 @@ const Pagination: React.FC = ({ )); }; + if (!totalPages) { + return null; + } + return (
{currentPage !== 1 && ( diff --git a/src/common/components/elements/SearchBar.tsx b/src/common/components/elements/SearchBar.tsx new file mode 100644 index 00000000..9a0ed536 --- /dev/null +++ b/src/common/components/elements/SearchBar.tsx @@ -0,0 +1,40 @@ +import { BiSearchAlt as SearchIcon, BiX as ClearIcon } from 'react-icons/bi'; + +interface SearchBarProps { + searchTerm: string; + onSearchChange: (event: React.ChangeEvent) => void; + onClearSearch: () => void; +} + +const SearchBar: React.FC = ({ + searchTerm, + onSearchChange, + onClearSearch, +}) => { + return ( +
+
+ + + {searchTerm && ( + + )} +
+
+ ); +}; + +export default SearchBar; diff --git a/src/common/styles/globals.css b/src/common/styles/globals.css index d843d50b..6092613d 100644 --- a/src/common/styles/globals.css +++ b/src/common/styles/globals.css @@ -41,7 +41,7 @@ a { #nprogress .bar { background: #15b8a6 !important; - height: 2px !important; + height: 1px !important; z-index: 9999999 !important; } diff --git a/src/modules/blog/components/BlogFeaturedSection.tsx b/src/modules/blog/components/BlogFeaturedSection.tsx index 465d75b7..de18d04c 100644 --- a/src/modules/blog/components/BlogFeaturedSection.tsx +++ b/src/modules/blog/components/BlogFeaturedSection.tsx @@ -20,6 +20,10 @@ const BlogFeaturedSection = () => { return []; }, [data]); + if (!featuredData || featuredData.length === 0) { + return null; + } + return ( <> {!isLoading ? ( diff --git a/src/modules/blog/components/BlogListNew.tsx b/src/modules/blog/components/BlogListNew.tsx index fd42f2bc..2cb4c077 100644 --- a/src/modules/blog/components/BlogListNew.tsx +++ b/src/modules/blog/components/BlogListNew.tsx @@ -2,9 +2,11 @@ import { motion } from 'framer-motion'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import useSWR from 'swr'; +import { useDebounce } from 'usehooks-ts'; import EmptyState from '@/common/components/elements/EmptyState'; import Pagination from '@/common/components/elements/Pagination'; +import SearchBar from '@/common/components/elements/SearchBar'; import BlogCardNewSkeleton from '@/common/components/skeleton/BlogCardNewSkeleton'; import { BlogItemProps } from '@/common/types/blog'; import { fetcher } from '@/services/fetcher'; @@ -14,41 +16,105 @@ import BlogFeaturedSection from './BlogFeaturedSection'; const BlogListNew = () => { const [page, setPage] = useState(1); + const [searchTerm, setSearchTerm] = useState(''); const router = useRouter(); + const debouncedSearchTerm = useDebounce(searchTerm, 500); + const { data, mutate, isLoading } = useSWR( - `/api/blog?page=${page}&per_page=6`, + `/api/blog?page=${page}&per_page=6&search=${debouncedSearchTerm}`, fetcher ); - const { posts: blogData = [], total_pages: totalPages = 1 } = - data?.data ?? {}; + const { + posts: blogData = [], + total_pages: totalPages = 1, + total_posts = 0, + } = data?.data || {}; - const handlePageChangeAndLoadData = async (newPage: number) => { + const handlePageChange = async (newPage: number) => { await mutate(); - router.push(`/blog?page=${newPage}`, undefined, { shallow: true }); + router.push( + { + pathname: '/blog', + query: { page: newPage, search: debouncedSearchTerm }, + }, + undefined, + { shallow: true } + ); setPage(newPage); }; + const handleSearch = (event: React.ChangeEvent) => { + const searchValue = event?.target?.value; + setSearchTerm(searchValue); + setPage(1); + + router.push( + { + pathname: '/blog', + query: searchValue ? { page: 1, search: searchValue } : { page: 1 }, + }, + undefined, + { shallow: true } + ); + }; + + const handleClearSearch = () => { + setSearchTerm(''); + setPage(1); + + router.push( + { + pathname: '/blog', + query: { page: 1 }, + }, + undefined, + { shallow: true } + ); + }; + useEffect(() => { const queryPage = Number(router.query.page); if (!isNaN(queryPage) && queryPage !== page) { setPage(queryPage); } - }, [page, router.query.page]); + }, [page, router.query.page, searchTerm]); const renderEmptyState = () => - !isLoading && data?.status === false && ; + !isLoading && + (!data?.status || blogData.length === 0) && ( + + ); return (
-
-

- Latest Articles -

+
+
+ {searchTerm ? ( +
+ + Search: + + {searchTerm} +
+ ) : ( +

+ Latest Articles +

+ )} + + {total_posts} + +
+
@@ -78,7 +144,7 @@ const BlogListNew = () => { )} diff --git a/src/pages/api/blog.ts b/src/pages/api/blog.ts index 3cd1204c..492014aa 100644 --- a/src/pages/api/blog.ts +++ b/src/pages/api/blog.ts @@ -14,12 +14,13 @@ export default async function handler( 'public, s-maxage=60, stale-while-revalidate=30' ); - const { page, per_page, categories } = req.query; + const { page, per_page, categories, search } = req.query; const responseData = await getBlogList({ page: Number(page) || 1, per_page: Number(per_page) || 9, categories: categories ? Number(categories) : undefined, + search: search ? String(search) : undefined, }); const blogItemsWithViews = await Promise.all( diff --git a/src/services/blog.ts b/src/services/blog.ts index a1f351af..2640b6f5 100644 --- a/src/services/blog.ts +++ b/src/services/blog.ts @@ -7,6 +7,7 @@ type BlogParamsProps = { page?: number; per_page?: number; categories?: number | undefined; + search?: string; }; interface BlogDetailResponseProps { @@ -51,9 +52,10 @@ export const getBlogList = async ({ page = 1, per_page = 6, categories, + search, }: BlogParamsProps): Promise<{ status: number; data: any }> => { try { - const params = { page, per_page, categories }; + const params = { page, per_page, categories, search }; const response = await axios.get(`${BLOG_URL}posts`, { params }); return { status: response?.status, data: extractData(response) }; } catch (error) {