-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* refactor: working project-list fetch * fix: typos
- Loading branch information
Showing
11 changed files
with
260 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
'use client'; | ||
|
||
import dynamic from 'next/dynamic'; | ||
|
||
import { ROUTE_SECTIONS } from '~/shared/core/constants'; | ||
|
||
import { projectFiltersSearchParamsAtom } from '~/projects/atoms/project-filters-search-params-atoms'; | ||
import { projectTotalCountAtom } from '~/projects/atoms/project-total-count-atom'; | ||
import { FiltersProvider } from '~/filters/providers/filters-provider'; | ||
|
||
interface Props { | ||
rawSearchParams: Record<string, string>; | ||
} | ||
|
||
export const ProjectListClientPage = ({ rawSearchParams }: Props) => { | ||
return ( | ||
<FiltersProvider | ||
rawSearchParams={rawSearchParams} | ||
routeSection={ROUTE_SECTIONS.PROJECTS} | ||
atom={projectFiltersSearchParamsAtom} | ||
> | ||
<FiltersSection | ||
countAtom={projectTotalCountAtom} | ||
searchPlaceholder="Search projects ..." | ||
/> | ||
|
||
<ProjectList /> | ||
</FiltersProvider> | ||
); | ||
}; | ||
|
||
const FiltersSection = dynamic(() => | ||
import('~/filters/components/filters-section').then((m) => m.FiltersSection), | ||
); | ||
|
||
const ProjectList = dynamic(() => | ||
import('~/projects/components/project-list').then((m) => m.ProjectList), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './page'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'; | ||
|
||
import { ROUTE_SECTIONS } from '~/shared/core/constants'; | ||
import { getQueryClient } from '~/shared/utils/get-query-client'; | ||
|
||
import { filterQueryKeys } from '~/filters/core/query-keys'; | ||
import { projectQueryKeys } from '~/projects/core/query-keys'; | ||
import { getFilterConfig } from '~/filters/api/get-filter-config'; | ||
import { getProjectList } from '~/projects/api/get-project-list'; | ||
|
||
import { ProjectListClientPage } from './client-page'; | ||
|
||
interface Props { | ||
searchParams: Record<string, string>; | ||
} | ||
|
||
const ProjectListPage = async ({ searchParams: rawSearchParams }: Props) => { | ||
const queryClient = getQueryClient(); | ||
|
||
await Promise.all([ | ||
// Prefetch list | ||
queryClient.prefetchInfiniteQuery({ | ||
queryKey: projectQueryKeys.list(rawSearchParams), | ||
queryFn: async ({ pageParam }) => | ||
getProjectList({ page: pageParam, searchParams: rawSearchParams }), | ||
initialPageParam: 1, | ||
}), | ||
// Prefetch filter config | ||
queryClient.prefetchQuery({ | ||
queryKey: filterQueryKeys.list(rawSearchParams, ROUTE_SECTIONS.PROJECTS), | ||
queryFn: () => getFilterConfig(`/${ROUTE_SECTIONS.PROJECTS}`), | ||
}), | ||
]); | ||
|
||
return ( | ||
<HydrationBoundary state={dehydrate(queryClient)}> | ||
<ProjectListClientPage rawSearchParams={rawSearchParams} /> | ||
</HydrationBoundary> | ||
); | ||
}; | ||
export default ProjectListPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
interface Props { | ||
children: React.ReactNode; | ||
list: React.ReactNode; | ||
} | ||
|
||
const ProjectsLayout = ({ children, list }: Props) => { | ||
return ( | ||
<div className="min-h-screen w-full md:pl-[212px]"> | ||
<div className="w-full lg:w-1/2"> | ||
<div className="flex flex-col gap-8 px-2 py-8 pt-24 md:px-8 md:pt-8"> | ||
{list} | ||
</div> | ||
</div> | ||
|
||
{children} | ||
</div> | ||
); | ||
}; | ||
|
||
export default ProjectsLayout; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,3 @@ | ||
const ProjectListPage = () => { | ||
return ( | ||
<div className="flex min-h-screen w-full items-center justify-center"> | ||
ProjectListPage | ||
</div> | ||
); | ||
}; | ||
const ProjectListPage = () => null; | ||
|
||
export default ProjectListPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { atom } from 'jotai'; | ||
|
||
import { ProjectDetails } from '~/projects/core/schemas'; | ||
|
||
export const initProjectAtom = atom<ProjectDetails | null>(null); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { atom } from 'jotai'; | ||
|
||
export const projectFiltersSearchParamsAtom = atom<URLSearchParams>( | ||
new URLSearchParams(), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { atom } from 'jotai'; | ||
|
||
export const projectTotalCountAtom = atom<number | null>(null); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
'use client'; | ||
|
||
import { cn } from '~/shared/utils/cn'; | ||
import { reloadPage } from '~/shared/utils/reload-page'; | ||
import { InternalErrorResult } from '~/shared/components/internal-error-result'; | ||
import { VirtualWrapper } from '~/shared/components/virtual-wrapper'; | ||
|
||
import { useProjectList } from '~/projects/hooks/use-project-list'; | ||
import { useFiltersContext } from '~/filters/providers/filters-provider/context'; | ||
|
||
export const ProjectList = () => { | ||
const { | ||
projects, | ||
error, | ||
inViewRef, | ||
hasNextPage, | ||
isSuccess, | ||
isPending: isPendingList, | ||
} = useProjectList(); | ||
const hasProjects = projects.length > 0; | ||
|
||
const { isPendingFilters, filterSearchParams } = useFiltersContext(); | ||
|
||
const isPending = [isPendingFilters, isPendingList].includes(true); | ||
|
||
const filterParamsString = | ||
filterSearchParams.size > 0 ? `${filterSearchParams.toString()}` : ''; | ||
|
||
return ( | ||
<> | ||
{error && <InternalErrorResult onReset={reloadPage} />} | ||
|
||
{isPending ? ( | ||
<p>Card Skeleton</p> | ||
) : isSuccess && hasProjects ? ( | ||
<> | ||
<div> | ||
<p>Init project card</p> | ||
<p>{JSON.stringify({ filterParamsString })}</p> | ||
</div> | ||
|
||
<VirtualWrapper count={projects.length}> | ||
{(index) => ( | ||
<div className={cn({ 'pt-8': index > 0 })}> | ||
<div className="flex flex-col bg-darkest-gray p-4"> | ||
<pre>{JSON.stringify(projects[index], undefined, '\t')}</pre> | ||
</div> | ||
</div> | ||
)} | ||
</VirtualWrapper> | ||
|
||
{hasNextPage ? ( | ||
<div ref={inViewRef}> | ||
<p>Card Skeleton</p> | ||
</div> | ||
) : ( | ||
<p>TODO: No more projects UI</p> | ||
)} | ||
</> | ||
) : ( | ||
<p>Empty UI</p> | ||
)} | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { useEffect } from 'react'; | ||
import { useInView } from 'react-intersection-observer'; | ||
|
||
import { useAtom } from 'jotai'; | ||
|
||
import { HREFS } from '~/shared/core/constants'; | ||
import { getQueryClient } from '~/shared/utils/get-query-client'; | ||
import { initPathAtom } from '~/shared/atoms/init-path-atom'; | ||
|
||
import { projectQueryKeys } from '~/projects/core/query-keys'; | ||
import { initProjectAtom } from '~/projects/atoms/init-project-atom'; | ||
import { projectTotalCountAtom } from '~/projects/atoms/project-total-count-atom'; | ||
import { getProjectDetails } from '~/projects/api/get-project-details'; | ||
|
||
import { useProjectListQuery } from './use-project-list-query'; | ||
|
||
export const useProjectList = () => { | ||
const queryClient = getQueryClient(); | ||
|
||
const { | ||
data, | ||
error, | ||
fetchNextPage, | ||
hasNextPage, | ||
isSuccess, | ||
isPending, | ||
isFetching, | ||
} = useProjectListQuery(); | ||
|
||
// Sync total count | ||
const [totalCount, setTotalCount] = useAtom(projectTotalCountAtom); | ||
useEffect(() => { | ||
const currentTotal = data?.pages[0].total ?? 0; | ||
if (data && currentTotal !== totalCount) { | ||
setTotalCount(currentTotal); | ||
} | ||
}, [data, setTotalCount, totalCount]); | ||
|
||
// Prefetch project details | ||
useEffect(() => { | ||
if (isSuccess && data) { | ||
const items = data.pages.flatMap((d) => d.data); | ||
for (const item of items) { | ||
const { id } = item; | ||
queryClient.prefetchQuery({ | ||
queryKey: projectQueryKeys.details(id), | ||
queryFn: () => getProjectDetails({ projectId: id }), | ||
}); | ||
} | ||
} | ||
}, [data, isSuccess, queryClient]); | ||
|
||
// Next page fetch on scroll | ||
const { ref: inViewRef } = useInView({ | ||
threshold: 1, | ||
onChange: (inView) => { | ||
if (inView && !error && !isFetching) fetchNextPage(); | ||
}, | ||
}); | ||
|
||
const [initPath] = useAtom(initPathAtom); | ||
const isProjectListSSR = initPath === HREFS.PROJECTS_PAGE; | ||
|
||
const [initProject] = useAtom(initProjectAtom); | ||
const allProjects = data?.pages.flatMap((d) => d.data) ?? []; | ||
|
||
// Dedupe init-card if not list-page ssr | ||
const projects = !isProjectListSSR | ||
? allProjects.filter((d) => d.id !== initProject?.id) | ||
: allProjects; | ||
|
||
return { | ||
projects, | ||
error, | ||
inViewRef, | ||
hasNextPage, | ||
isSuccess, | ||
isPending, | ||
}; | ||
}; |