diff --git a/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.Skeleton.tsx b/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.Skeleton.tsx index 994ef22..7bd876a 100644 --- a/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.Skeleton.tsx +++ b/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.Skeleton.tsx @@ -3,7 +3,7 @@ import Skeleton from 'react-loading-skeleton' export default function ProjectCardMiniSkeleton() { return ( -
+

diff --git a/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.tsx b/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.tsx index cd6e200..3fd1d9f 100644 --- a/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.tsx +++ b/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.tsx @@ -12,7 +12,7 @@ export default function ProjectCardMini({ project, onClick }: Props) { return (
{ e.key !== 'Enter' || onClick(project?.id!) }} diff --git a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx index d98d85f..c1d22aa 100644 --- a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx +++ b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx @@ -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>('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 | null>(null) const [selectedCategory, setSelectedCategory] = useState(null) + const projectsLength = useRef(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() {
p !== null) as any[] ?? []} /> + {canLoadMore &&
+ +
}
diff --git a/src/features/Projects/pages/ExplorePage/ProjectsGrid/ProjectsGrid.tsx b/src/features/Projects/pages/ExplorePage/ProjectsGrid/ProjectsGrid.tsx index ba5c063..f8b54b3 100644 --- a/src/features/Projects/pages/ExplorePage/ProjectsGrid/ProjectsGrid.tsx +++ b/src/features/Projects/pages/ExplorePage/ProjectsGrid/ProjectsGrid.tsx @@ -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) => )} {!isLoading && projects.length === 0 &&

No results found here...

} {!isLoading && projects.map((project) => )} + + {isLoadingMore && Array(4).fill(0).map((_, idx) => )}
) } diff --git a/src/utils/apollo.ts b/src/utils/apollo.ts index acaba74..647180f 100644 --- a/src/utils/apollo.ts +++ b/src/utils/apollo.ts @@ -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( 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