From 6db1b441f0ae50de278fe8a329f55f5a74b8a030 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sun, 8 Dec 2024 08:36:04 +0100 Subject: [PATCH] NORY-15: implement infinite scroll for identities --- dashboard/src/app/user/data-table.tsx | 46 ++++++++++++++++++++++--- dashboard/src/app/user/page.tsx | 4 +-- dashboard/src/components/ui/spinner.tsx | 20 +++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 dashboard/src/components/ui/spinner.tsx diff --git a/dashboard/src/app/user/data-table.tsx b/dashboard/src/app/user/data-table.tsx index e40e332..49cdefb 100644 --- a/dashboard/src/app/user/data-table.tsx +++ b/dashboard/src/app/user/data-table.tsx @@ -5,8 +5,9 @@ import { Identity } from '@ory/client'; import { DataTable } from '@/components/ui/data-table'; import { CircleCheck, CircleX } from 'lucide-react'; import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'; -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { FetchIdentityPageProps } from '@/app/user/page'; +import { Spinner } from '@/components/ui/spinner'; interface IdentityDataTableProps { data: Identity[]; @@ -89,8 +90,12 @@ export function IdentityDataTable({ data, pageSize, pageToken, query, fetchIdent const [items, setItems] = useState(data); const [nextToken, setNextToken] = useState(pageToken); - const fetchMore = async () => { + useEffect(() => { + setItems(data); + setNextToken(pageToken); + }, [data, pageSize, pageToken, query]); + const fetchMore = async () => { if (!nextToken) return; const response = await fetchIdentityPage({ @@ -103,6 +108,39 @@ export function IdentityDataTable({ data, pageSize, pageToken, query, fetchIdent setNextToken(response.tokens.get('next') ?? undefined); }; - // TODO: fetch more when scrolling to the end of list - return ; + const infiniteScrollSensor = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting) { + fetchMore(); + } + }, + { threshold: 0.5 }, // Adjust threshold as needed + ); + + if (infiniteScrollSensor.current) { + observer.observe(infiniteScrollSensor.current); + } + + return () => { + if (infiniteScrollSensor.current) { + observer.unobserve(infiniteScrollSensor.current); + } + }; + }, [items]); + + return ( + <> + + { + nextToken && ( +
+ +
+ ) + } + + ); } diff --git a/dashboard/src/app/user/page.tsx b/dashboard/src/app/user/page.tsx index 3938d42..b6bfa01 100644 --- a/dashboard/src/app/user/page.tsx +++ b/dashboard/src/app/user/page.tsx @@ -56,10 +56,10 @@ export default async function UserPage( const params = await searchParams; const query = params.query ? params.query as string : ''; - let pageSize = 200; + let pageSize = 250; let pageToken: string = '00000000-0000-0000-0000-000000000000'; - const initialFetch = await fetchIdentityPage({ pageSize, pageToken, query: '' }); + const initialFetch = await fetchIdentityPage({ pageSize, pageToken, query: query }); return (
diff --git a/dashboard/src/components/ui/spinner.tsx b/dashboard/src/components/ui/spinner.tsx new file mode 100644 index 0000000..e1bfb8b --- /dev/null +++ b/dashboard/src/components/ui/spinner.tsx @@ -0,0 +1,20 @@ +import { cn } from '@/lib/utils'; +import { RefObject } from 'react'; + +export const Spinner = ( + { className, ref }: { className?: string, ref: RefObject }, +) => + +;