feat: add pagination

This commit is contained in:
MTG2000
2022-10-23 14:03:25 +03:00
parent c540d07ab0
commit 5752f7fdbf
5 changed files with 41 additions and 10 deletions

View File

@@ -3,7 +3,7 @@ import Skeleton from 'react-loading-skeleton'
export default function ProjectCardMiniSkeleton() {
return (
<div className="bg-gray-25 select-none px-16 py-16 flex flex-[0_0_100%] gap-16 border border-gray-200 rounded-10 items-center" >
<div className="select-none px-16 py-16 flex flex-[0_0_100%] gap-16 rounded-10 items-center" >
<Skeleton circle width={64} height={64} containerClassName='flex-shrink-0' />
<div className="justify-around items-start min-w-0">
<p className="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap"><Skeleton width="15ch" /></p>

View File

@@ -12,7 +12,7 @@ export default function ProjectCardMini({ project, onClick }: Props) {
return (
<div
className="py-16 select-none px-16 flex items-center gap-16 rounded-16 hover:bg-gray-50 hover:outline outline-1 outline-gray-200"
className="py-16 select-none px-16 flex items-center gap-16 rounded-16 hover:bg-gray-50 hover:outline active:scale-95 transition-transform outline-1 outline-gray-200"
onKeyDown={e => {
e.key !== 'Enter' || onClick(project?.id!)
}}

View File

@@ -4,7 +4,7 @@ import { useExplorePageQuery } from 'src/graphql';
import ProjectsGrid from './ProjectsGrid/ProjectsGrid';
import { Helmet } from "react-helmet";
import Categories, { Category } from '../../Components/Categories/Categories';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import Header from './Header/Header';
import Button from 'src/Components/Button/Button';
import { useAppDispatch } from 'src/utils/hooks';
@@ -13,10 +13,12 @@ import { createAction } from '@reduxjs/toolkit';
import { useReduxEffect } from 'src/utils/hooks/useReduxEffect';
import { NetworkStatus } from '@apollo/client';
import { FiSliders } from 'react-icons/fi';
import { HiOutlineChevronDoubleDown } from 'react-icons/hi'
import { ProjectsFilters } from './Filters/FiltersModal';
const UPDATE_FILTERS_ACTION = createAction<Partial<ProjectsFilters>>('PROJECTS_FILTERS_UPDATED')({})
type QueryFilter = Partial<{
categoryId: string[] | null
tags: string[] | null
@@ -25,11 +27,15 @@ type QueryFilter = Partial<{
license: string | null
}>
const PAGE_SIZE = 20;
export default function ExplorePage() {
const dispatch = useAppDispatch();
const [filters, setFilters] = useState<Partial<ProjectsFilters> | null>(null)
const [selectedCategory, setSelectedCategory] = useState<Category | null>(null)
const projectsLength = useRef<number>(0);
const [canFetchMore, setCanFetchMore] = useState(true);
const { queryFilters, hasSearchFilters } = useMemo(() => {
let filter: QueryFilter = {}
@@ -68,23 +74,30 @@ export default function ExplorePage() {
return { queryFilters: filter, hasSearchFilters };
}, [filters, selectedCategory?.id])
const { data, networkStatus, error } = useExplorePageQuery({
const { data, networkStatus, error, fetchMore } = useExplorePageQuery({
variables: {
page: 1,
pageSize: 20,
pageSize: PAGE_SIZE,
filter: queryFilters
},
notifyOnNetworkStatusChange: true,
onCompleted: data => {
if ((data.projects?.length ?? 0) < PAGE_SIZE) setCanFetchMore(false);
}
});
projectsLength.current = data?.projects?.length ?? 0;
const onFiltersUpdated = useCallback(({ payload }: typeof UPDATE_FILTERS_ACTION) => {
setSelectedCategory(null)
setCanFetchMore(true);
if (Object.keys(payload).length === 0)
setFilters(null);
else
setFilters(payload);
}, [])
useReduxEffect(onFiltersUpdated, UPDATE_FILTERS_ACTION.type)
@@ -110,6 +123,14 @@ export default function ExplorePage() {
const selectCategoryTab = (category: Category | null) => {
setSelectedCategory(category);
setCanFetchMore(true);
}
const clickFetchMore = () => {
fetchMore({ variables: { page: Math.floor((data?.projects?.length ?? 0) / PAGE_SIZE) + 1 } })
.then(res => {
if (!res.data.projects || res.data.projects.length < PAGE_SIZE) setCanFetchMore(false);
})
}
if (error) {
@@ -120,6 +141,8 @@ export default function ExplorePage() {
const isLoading = networkStatus === NetworkStatus.loading || networkStatus === NetworkStatus.refetch || networkStatus === NetworkStatus.setVariables;
const isLoadingMore = networkStatus === NetworkStatus.fetchMore;
const canLoadMore = !isLoading && !isLoadingMore && data?.projects && data.projects.length > 0 && canFetchMore;
return (
@@ -146,8 +169,12 @@ export default function ExplorePage() {
<div className="mt-40">
<ProjectsGrid
isLoading={isLoading}
isLoadingMore={isLoadingMore}
projects={data?.projects?.filter((p) => p !== null) as any[] ?? []}
/>
{canLoadMore && <div className="flex justify-center mt-36">
<Button onClick={clickFetchMore} color="gray"><HiOutlineChevronDoubleDown /> Load more</Button>
</div>}
</div>
</div>
</>

View File

@@ -7,10 +7,11 @@ import { ProjectCard } from 'src/utils/interfaces';
interface Props {
isLoading?: boolean;
isLoadingMore?: boolean;
projects: ProjectCard[]
}
export default function ProjectsGrid({ isLoading, projects }: Props) {
export default function ProjectsGrid({ isLoading, isLoadingMore, projects }: Props) {
const dispatch = useAppDispatch();
@@ -33,6 +34,8 @@ export default function ProjectsGrid({ isLoading, projects }: Props) {
{isLoading && Array(12).fill(0).map((_, idx) => <ProjectCardMiniSkeleton key={idx} />)}
{!isLoading && projects.length === 0 && <p className="text-center text-gray-400 py-48 text-body2 font-medium col-span-full">No results found here...</p>}
{!isLoading && projects.map((project) => <ProjectCardMini key={project.id} project={project} onClick={handleClick} />)}
{isLoadingMore && Array(4).fill(0).map((_, idx) => <ProjectCardMiniSkeleton key={idx} />)}
</div>
)
}

View File

@@ -54,7 +54,7 @@ export const apolloClient = new ApolloClient({
typePolicies: {
Query: {
fields: {
getFeed: offsetLimitPagination(['sortBy', 'tag'])
projects: offsetLimitPagination(['_filter'])
},
},
},
@@ -72,10 +72,11 @@ function offsetLimitPagination<T = Reference>(
const merged = existing ? existing.slice(0) : [];
if (args) {
// Assume an skip of 0 if args.skip omitted.
const { skip = 0 } = args;
// Assume an _page of 0 if args._page omitted.
const { _page = 1, _page_size = 20 } = args;
const offset = (_page - 1) * _page_size;
for (let i = 0; i < incoming.length; ++i) {
merged[skip + i] = incoming[i];
merged[offset + i] = incoming[i];
}
} else {
// It's unusual (probably a mistake) for a paginated field not