diff --git a/dashboard/src/app/(inside)/client/data-table.tsx b/dashboard/src/app/(inside)/client/data-table.tsx new file mode 100644 index 0000000..2e347db --- /dev/null +++ b/dashboard/src/app/(inside)/client/data-table.tsx @@ -0,0 +1,105 @@ +'use client'; + +import { ColumnDef } from '@tanstack/react-table'; +import { OAuth2Client } from '@ory/client'; +import { DataTable } from '@/components/ui/data-table'; +import { useEffect, useRef, useState } from 'react'; +import { Spinner } from '@/components/ui/spinner'; +import { FetchClientPageProps } from '@/app/(inside)/client/page'; + +interface ClientDataTableProps { + data: OAuth2Client[]; + pageSize: number; + pageToken: string | undefined; + fetchClientPage: (props: FetchClientPageProps) => Promise<{ + data: OAuth2Client[], + tokens: Map + }>; +} + +export function ClientDataTable( + { + data, + pageSize, + pageToken, + fetchClientPage, + }: ClientDataTableProps, +) { + + console.log('OAuth2 client', data); + + const columns: ColumnDef[] = [ + { + id: 'client_id', + accessorKey: 'client_id', + header: 'Client ID', + }, + { + id: 'client_name', + accessorKey: 'client_name', + header: 'Client Name', + }, + { + id: 'owner', + accessorKey: 'owner', + header: 'Owner', + }, + ]; + + const [items, setItems] = useState(data); + const [nextToken, setNextToken] = useState(pageToken); + + // react on changes from ssr (query params) + useEffect(() => { + setItems(data); + setNextToken(pageToken); + }, [data, pageSize, pageToken]); + + // infinite scroll handling + 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]); + + const fetchMore = async () => { + if (!nextToken) return; + + const response = await fetchClientPage({ + pageSize: pageSize, + pageToken: nextToken, + }); + + setItems([...items, ...response.data]); + setNextToken(response.tokens.get('next') ?? undefined); + }; + + return ( + <> + + { + nextToken && ( +
+ +
+ ) + } + + ); +} diff --git a/dashboard/src/app/(inside)/client/page.tsx b/dashboard/src/app/(inside)/client/page.tsx index db0117a..439fd3e 100644 --- a/dashboard/src/app/(inside)/client/page.tsx +++ b/dashboard/src/app/(inside)/client/page.tsx @@ -1,12 +1,47 @@ +import { getOAuth2Api } from '@/ory/sdk/server'; +import { parseTokens } from '@/app/(inside)/user/page'; +import { ClientDataTable } from '@/app/(inside)/client/data-table'; + +export interface FetchClientPageProps { + pageSize: number; + pageToken: string; +} + +async function fetchClientPage({ pageSize, pageToken }: FetchClientPageProps) { + 'use server'; + + const oAuth2Api = await getOAuth2Api(); + const response = await oAuth2Api.listOAuth2Clients({ + pageSize: pageSize, + pageToken: pageToken, + }); + + return { + data: response.data, + tokens: parseTokens(response.headers.link), + }; +} + export default async function ListClientPage() { + + let pageSize = 100; + let pageToken: string = '00000000-0000-0000-0000-000000000000'; + + const initialFetch = await fetchClientPage({ pageSize, pageToken }); + return (

OAuth2 Clients

- Part of milestone v0.2.0 + See and manage all OAuth2 clients registered with your Ory Hydra instance

+
); }