diff --git a/apps/web/core/components/pages/list/loader.tsx b/apps/web/core/components/pages/list/loader.tsx
new file mode 100644
index 0000000000..6c504d9b3e
--- /dev/null
+++ b/apps/web/core/components/pages/list/loader.tsx
@@ -0,0 +1,15 @@
+import { Loader } from "@plane/ui";
+
+export function PageListLoader() {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/core/components/pages/list/root.tsx b/apps/web/core/components/pages/list/root.tsx
index 0d477745fe..175d4b3412 100644
--- a/apps/web/core/components/pages/list/root.tsx
+++ b/apps/web/core/components/pages/list/root.tsx
@@ -1,13 +1,19 @@
+import { useCallback, useRef, useState } from "react";
import { observer } from "mobx-react";
+import { useParams } from "next/navigation";
// types
import type { TPageNavigationTabs } from "@plane/types";
+import { Loader } from "@plane/ui";
// components
import { ListLayout } from "@/components/core/list";
+// hooks
+import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
// plane web hooks
import type { EPageStoreType } from "@/plane-web/hooks/store";
import { usePageStore } from "@/plane-web/hooks/store";
// local imports
import { PageListBlock } from "./block";
+import { PageListLoader } from "./loader";
type TPagesListRoot = {
pageType: TPageNavigationTabs;
@@ -16,17 +22,58 @@ type TPagesListRoot = {
export const PagesListRoot = observer(function PagesListRoot(props: TPagesListRoot) {
const { pageType, storeType } = props;
+ // params
+ const { workspaceSlug, projectId } = useParams();
// store hooks
- const { getCurrentProjectFilteredPageIdsByTab } = usePageStore(storeType);
+ const { getCurrentProjectFilteredPageIdsByTab, fetchPagesList, getPaginationInfo, getPaginationLoader } =
+ usePageStore(storeType);
+
+ // pagination hooks
+ const paginationInfo = getPaginationInfo(pageType);
+ const paginationLoader = getPaginationLoader(pageType);
+ const hasNextPage = paginationInfo.hasNextPage;
+ const isFetchingNextPage = paginationLoader === "pagination";
+
+ // state for intersection observer element
+ const [intersectionElement, setIntersectionElement] = useState(null);
+ // ref for container - we'll wrap ListLayout
+ const containerRef = useRef(null);
+
// derived values
const filteredPageIds = getCurrentProjectFilteredPageIdsByTab(pageType);
+ // Function to fetch next page
+ const fetchNextPage = useCallback(() => {
+ if (!workspaceSlug || !projectId || !hasNextPage || isFetchingNextPage) {
+ return;
+ }
+ // Use fetchPagesList with cursor from pagination info
+ void fetchPagesList(
+ workspaceSlug.toString(),
+ projectId.toString(),
+ pageType,
+ paginationInfo.nextCursor ?? undefined
+ );
+ }, [workspaceSlug, projectId, hasNextPage, isFetchingNextPage, fetchPagesList, pageType, paginationInfo.nextCursor]);
+
+ // Set up intersection observer to trigger loading more pages
+ useIntersectionObserver(
+ containerRef,
+ isFetchingNextPage ? null : intersectionElement,
+ fetchNextPage,
+ `100% 0% 100% 0%`
+ );
+
if (!filteredPageIds) return <>>;
+
return (
-
- {filteredPageIds.map((pageId) => (
-
- ))}
-
+
+
+ {filteredPageIds.map((pageId) => (
+
+ ))}
+ {hasNextPage && }
+
+
);
});
diff --git a/apps/web/core/components/pages/pages-list-main-content.tsx b/apps/web/core/components/pages/pages-list-main-content.tsx
index 58e239140b..8396e53c78 100644
--- a/apps/web/core/components/pages/pages-list-main-content.tsx
+++ b/apps/web/core/components/pages/pages-list-main-content.tsx
@@ -156,6 +156,11 @@ export const PagesListMainContent = observer(function PagesListMainContent(props
/>
);
}
+
+ if (loader === "mutation-loader" && (!filteredPageIds || filteredPageIds.length === 0)) {
+ return ;
+ }
+
// if no pages match the filter criteria
if (filteredPageIds?.length === 0)
return (
diff --git a/apps/web/core/components/pages/pages-list-view.tsx b/apps/web/core/components/pages/pages-list-view.tsx
index 15f114f46e..86e3e25dda 100644
--- a/apps/web/core/components/pages/pages-list-view.tsx
+++ b/apps/web/core/components/pages/pages-list-view.tsx
@@ -19,11 +19,20 @@ type TPageView = {
export const PagesListView = observer(function PagesListView(props: TPageView) {
const { children, pageType, projectId, storeType, workspaceSlug } = props;
// store hooks
- const { isAnyPageAvailable, fetchPagesList } = usePageStore(storeType);
- // fetching pages list
+ const { isAnyPageAvailable, fetchPagesList, filters } = usePageStore(storeType);
+
+ // fetching pages list - include filters and sorting in SWR key to refetch when they change
useSWR(
- workspaceSlug && projectId && pageType ? `PROJECT_PAGES_${projectId}` : null,
- workspaceSlug && projectId && pageType ? () => fetchPagesList(workspaceSlug, projectId, pageType) : null
+ workspaceSlug && projectId && pageType
+ ? `PROJECT_PAGES_${projectId}_${pageType}_${filters.searchQuery || ""}_${filters.sortKey || ""}_${
+ filters.sortBy || ""
+ }_${JSON.stringify(filters.filters || {})}`
+ : null,
+ workspaceSlug && projectId && pageType ? () => fetchPagesList(workspaceSlug, projectId, pageType) : null,
+ {
+ revalidateOnFocus: true,
+ revalidateIfStale: true,
+ }
);
// pages loader