From 22cbe40e509e12beceddb4f432c0f9bcb7d3c8c5 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Tue, 5 May 2026 13:00:40 -0400 Subject: [PATCH 1/7] initial drafts for home page --- apps/webapp/src/app/globals.css | 4 +- apps/webapp/src/app/layout.tsx | 6 ++- apps/webapp/src/app/page.tsx | 11 +----- .../home/hooks/use-registered-hosts.ts | 37 ++++++++++++++++++ .../home/hooks/use-registered-indexers.ts | 39 +++++++++++++++++++ apps/webapp/src/features/home/index.ts | 0 apps/webapp/src/features/home/ui/home.tsx | 15 +++++++ .../src/features/home/ui/hosts-home.tsx | 39 +++++++++++++++++++ .../src/features/home/ui/indexers-home.tsx | 35 +++++++++++++++++ .../src/features/home/ui/stats-home.tsx | 26 +++++++++++++ .../src/features/home/ui/title-home.tsx | 14 +++++++ .../assertion/ui/assertion.tsx | 6 +-- apps/webapp/src/page-components/home/index.ts | 1 + .../src/page-components/home/ui/page.tsx | 11 ++++++ .../page-components/registration/ui/page.tsx | 6 +-- apps/webapp/src/shared/lib/constants/index.ts | 12 +++++- .../src/widget/form-header/ui/form-header.tsx | 4 +- 17 files changed, 243 insertions(+), 23 deletions(-) create mode 100644 apps/webapp/src/features/home/hooks/use-registered-hosts.ts create mode 100644 apps/webapp/src/features/home/hooks/use-registered-indexers.ts create mode 100644 apps/webapp/src/features/home/index.ts create mode 100644 apps/webapp/src/features/home/ui/home.tsx create mode 100644 apps/webapp/src/features/home/ui/hosts-home.tsx create mode 100644 apps/webapp/src/features/home/ui/indexers-home.tsx create mode 100644 apps/webapp/src/features/home/ui/stats-home.tsx create mode 100644 apps/webapp/src/features/home/ui/title-home.tsx create mode 100644 apps/webapp/src/page-components/home/index.ts create mode 100644 apps/webapp/src/page-components/home/ui/page.tsx diff --git a/apps/webapp/src/app/globals.css b/apps/webapp/src/app/globals.css index 027a8645..a875f78a 100644 --- a/apps/webapp/src/app/globals.css +++ b/apps/webapp/src/app/globals.css @@ -82,9 +82,9 @@ @apply border-border outline-ring/50; } html { - @apply bg-background; + @apply overflow-x-hidden bg-background; } body { - @apply bg-background text-foreground; + @apply overflow-x-hidden bg-background text-foreground; } } diff --git a/apps/webapp/src/app/layout.tsx b/apps/webapp/src/app/layout.tsx index ccac0d99..30ca10d2 100644 --- a/apps/webapp/src/app/layout.tsx +++ b/apps/webapp/src/app/layout.tsx @@ -33,13 +33,15 @@ export default function RootLayout({ return ( -
{children}
+
+ {children} +
diff --git a/apps/webapp/src/app/page.tsx b/apps/webapp/src/app/page.tsx index 4ee1b8ff..9ea7a17f 100644 --- a/apps/webapp/src/app/page.tsx +++ b/apps/webapp/src/app/page.tsx @@ -1,10 +1 @@ -import { isRegistrationV2 } from "@/shared/lib"; -import { Header } from "@/widget"; -import { redirect } from "next/navigation"; - -export default function HomePage() { - if (!isRegistrationV2()) { - return redirect("/registration"); - } - return
; -} +export { HomePage as default } from "@/page-components/home"; \ No newline at end of file diff --git a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts new file mode 100644 index 00000000..0e614176 --- /dev/null +++ b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts @@ -0,0 +1,37 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; + +const registeredHostsApiEndpoint = process.env.NEXT_PUBLIC_REGISTERED_HOSTS_API_ENDPOINT || "http://rpc.develop.devnet.shinzo.network:1317/shinzonetwork/host/v1/hosts"; + +interface RegisteredHost { + address: string; + did: string; + connection_string: string; +} + +interface IndexerResponse { + hosts: RegisteredHost[]; + pagination: { + total: number; + next_key: string | null; + }; +} + +async function fetchRegisteredHosts(): Promise { + const response = await fetch(registeredHostsApiEndpoint); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const data: IndexerResponse = await response.json(); + + return data; +} + +export function useRegisteredHosts(intervalMs = 5000) { + return useQuery({ + queryKey: ["registered-hosts"], + queryFn: fetchRegisteredHosts, + refetchInterval: intervalMs, + refetchIntervalInBackground: true, + }); +} diff --git a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts new file mode 100644 index 00000000..f72b2ad8 --- /dev/null +++ b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts @@ -0,0 +1,39 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; + +const registeredIndexerApiEndpoint = process.env.NEXT_PUBLIC_REGISTERED_INDEXER_API_ENDPOINT || "http://rpc.develop.devnet.shinzo.network:1317/shinzonetwork/indexer/v1/indexers"; + +interface RegisteredIndexer { + address: string; + did: string; + connection_string: string; + source_chain: string; + source_chain_id: string; +} + +type IndexerResponse = { + indexers: RegisteredIndexer[]; + pagination: { + total: number; + next_key: string | null; + }; +} + +async function fetchRegisteredIndexers(): Promise { + const response = await fetch(registeredIndexerApiEndpoint); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const data: IndexerResponse = await response.json(); + + return data; +} + +export function useRegisteredIndexers(intervalMs = 5000) { + return useQuery({ + queryKey: ["registered-indexers"], + queryFn: fetchRegisteredIndexers, + refetchInterval: intervalMs, + refetchIntervalInBackground: true, + }); +} diff --git a/apps/webapp/src/features/home/index.ts b/apps/webapp/src/features/home/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/apps/webapp/src/features/home/ui/home.tsx b/apps/webapp/src/features/home/ui/home.tsx new file mode 100644 index 00000000..300cf4bd --- /dev/null +++ b/apps/webapp/src/features/home/ui/home.tsx @@ -0,0 +1,15 @@ +import { HostsHome } from "./hosts-home"; +import { IndexersHome } from "./indexers-home"; +import { StatsHome } from "./stats-home"; +import { TitleHome } from "./title-home"; + +export function Home() { + return ( +
+ + + + +
+ ); +} \ No newline at end of file diff --git a/apps/webapp/src/features/home/ui/hosts-home.tsx b/apps/webapp/src/features/home/ui/hosts-home.tsx new file mode 100644 index 00000000..2e70598c --- /dev/null +++ b/apps/webapp/src/features/home/ui/hosts-home.tsx @@ -0,0 +1,39 @@ +"use client"; +import { useRegisteredHosts } from "../hooks/use-registered-hosts"; + +export function HostsHome() { + const { data: registeredHosts } = useRegisteredHosts(); + const hosts = registeredHosts?.hosts || []; + + return ( +
+
+
+

Registered Hosts

+

INFRASTRUCTURE_LAYER / DATA_AVAILABILITY

+
+ +
+
+ + + + + + + + + + {hosts.map((host) => ( + + + + + + ))} + +
ADDRESSDIDCONNECTION STRING
{host.address}{host.did}{host.connection_string}
+
+
+ ); +} \ No newline at end of file diff --git a/apps/webapp/src/features/home/ui/indexers-home.tsx b/apps/webapp/src/features/home/ui/indexers-home.tsx new file mode 100644 index 00000000..d69e3861 --- /dev/null +++ b/apps/webapp/src/features/home/ui/indexers-home.tsx @@ -0,0 +1,35 @@ +export function IndexersHome() { + return ( +
+
+
+

Registered Indexers

+

NETWORK_LAYER / INDEXING_SERVICES

+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
ADDRESSDIDCONNECTION STRINGSOURCE CHAINCHAIN ID
0x71C...4e31did:eth:0x71...wss://indexer-01.shinzo.ioEthereum Mainnet1
+
+
+ ); +} \ No newline at end of file diff --git a/apps/webapp/src/features/home/ui/stats-home.tsx b/apps/webapp/src/features/home/ui/stats-home.tsx new file mode 100644 index 00000000..0dbae356 --- /dev/null +++ b/apps/webapp/src/features/home/ui/stats-home.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { UI_HOME_HEADER_CONTENT } from "@/shared/lib"; +import { useRegisteredHosts } from "../hooks/use-registered-hosts"; +import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; + +export function StatsHome() { + const { data: registeredIndexers } = useRegisteredIndexers(); + const { data: registeredHosts } = useRegisteredHosts(); + + const totalIndexers = registeredIndexers?.pagination.total || 0; + const totalHosts = registeredHosts?.pagination.total || 0; + + return ( +
+
+
{UI_HOME_HEADER_CONTENT.registered_indexers}
+
{totalIndexers}
+
+
+
{UI_HOME_HEADER_CONTENT.registered_hosts}
+
{totalHosts}
+
+
+ ); +} \ No newline at end of file diff --git a/apps/webapp/src/features/home/ui/title-home.tsx b/apps/webapp/src/features/home/ui/title-home.tsx new file mode 100644 index 00000000..4b2f9e8b --- /dev/null +++ b/apps/webapp/src/features/home/ui/title-home.tsx @@ -0,0 +1,14 @@ +import { UI_HOME_HEADER_CONTENT } from "@/shared/lib"; + +export function TitleHome() { + return ( +
+
+ + Active Network Infrastructure +
+

{UI_HOME_HEADER_CONTENT.home.title}

+

{UI_HOME_HEADER_CONTENT.home.description}

+
+ ); +} \ No newline at end of file diff --git a/apps/webapp/src/page-components/assertion/ui/assertion.tsx b/apps/webapp/src/page-components/assertion/ui/assertion.tsx index b6d6e840..f6da9a59 100644 --- a/apps/webapp/src/page-components/assertion/ui/assertion.tsx +++ b/apps/webapp/src/page-components/assertion/ui/assertion.tsx @@ -6,7 +6,7 @@ import { IndexerAssertionForm } from "@/features/indexer-assertion"; import { useRegistrationContext } from "@/entities/registration-process"; import { FormHeader } from "@/widget/form-header"; import { Header } from "@/widget"; -import { UI_TEXT_CONTENT, isRegistrationV2 } from "@/shared/lib"; +import { UI_FORM_HEADER_CONTENT, isRegistrationV2 } from "@/shared/lib"; import { usePathname } from "next/navigation"; import { Connect } from "@/page-components/connect"; import { Button } from "@/shared/ui/button"; @@ -23,8 +23,8 @@ export default function Assertion() {
diff --git a/apps/webapp/src/page-components/home/index.ts b/apps/webapp/src/page-components/home/index.ts new file mode 100644 index 00000000..bc441888 --- /dev/null +++ b/apps/webapp/src/page-components/home/index.ts @@ -0,0 +1 @@ +export { HomePage } from "./ui/page"; \ No newline at end of file diff --git a/apps/webapp/src/page-components/home/ui/page.tsx b/apps/webapp/src/page-components/home/ui/page.tsx new file mode 100644 index 00000000..c3c88182 --- /dev/null +++ b/apps/webapp/src/page-components/home/ui/page.tsx @@ -0,0 +1,11 @@ +import { Header } from "@/widget"; +import { Home } from "@/features/home/ui/home"; + +export function HomePage() { + return ( + <> +
+ + + ); +} diff --git a/apps/webapp/src/page-components/registration/ui/page.tsx b/apps/webapp/src/page-components/registration/ui/page.tsx index 9591dce9..a6aca597 100644 --- a/apps/webapp/src/page-components/registration/ui/page.tsx +++ b/apps/webapp/src/page-components/registration/ui/page.tsx @@ -9,7 +9,7 @@ import { } from "@/features/registration-form"; import { FormHeader } from "@/widget/form-header"; import { Header } from "@/widget"; -import { isRegistrationV2, UI_TEXT_CONTENT } from "@/shared/lib"; +import { isRegistrationV2, UI_FORM_HEADER_CONTENT } from "@/shared/lib"; import { usePathname } from "next/navigation"; import { Connect } from "@/page-components/connect"; @@ -24,8 +24,8 @@ export default function Register() {
diff --git a/apps/webapp/src/shared/lib/constants/index.ts b/apps/webapp/src/shared/lib/constants/index.ts index 912b39b7..2b9a75ce 100644 --- a/apps/webapp/src/shared/lib/constants/index.ts +++ b/apps/webapp/src/shared/lib/constants/index.ts @@ -9,7 +9,7 @@ export enum EntityRole { export const SHINZO_PRECOMPILE_ADDRESS = "0x0000000000000000000000000000000000000211"; -export const UI_TEXT_CONTENT = { +export const UI_FORM_HEADER_CONTENT = { "host-registration": { title: "/ Register your host to participate within the Shinzo Network", description: @@ -35,3 +35,13 @@ export const UI_TEXT_CONTENT = { export const isRegistrationV2 = () => { return process.env.NEXT_PUBLIC_SHINZOHUB_V2_REGISTRATION_FLAG === "true"; }; + +export const UI_HOME_HEADER_CONTENT = { + home: { + title: "TECHNICAL REGISTRY", + description: + "Verifiable data infrastructure for the decentralized web. A low-level coordination layer for decentralized indexing and host services.", + }, + registered_indexers: "REGISTERED INDEXERS", + registered_hosts: "REGISTERED HOSTS", +} as const; diff --git a/apps/webapp/src/widget/form-header/ui/form-header.tsx b/apps/webapp/src/widget/form-header/ui/form-header.tsx index ebe12062..7dd244a6 100644 --- a/apps/webapp/src/widget/form-header/ui/form-header.tsx +++ b/apps/webapp/src/widget/form-header/ui/form-header.tsx @@ -1,9 +1,9 @@ -import { UI_TEXT_CONTENT } from "@/shared/lib"; +import { UI_FORM_HEADER_CONTENT } from "@/shared/lib"; export function FormHeader({ content, }: { - content: (typeof UI_TEXT_CONTENT)[keyof typeof UI_TEXT_CONTENT]; + content: (typeof UI_FORM_HEADER_CONTENT)[keyof typeof UI_FORM_HEADER_CONTENT]; }) { return (
From 168ec668c07166f1adefde29016265a647b5fb55 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Thu, 7 May 2026 01:34:19 -0400 Subject: [PATCH 2/7] adding the footer and tables, updating teh styles --- apps/webapp/src/app/layout.tsx | 3 +- apps/webapp/src/app/page.tsx | 2 +- .../home/hooks/use-registered-hosts.ts | 4 +- .../home/hooks/use-registered-indexers.ts | 6 +- apps/webapp/src/features/home/ui/home.tsx | 10 +- .../src/features/home/ui/hosts-home.tsx | 88 +++++++++++------ .../src/features/home/ui/indexers-home.tsx | 97 +++++++++++++------ .../src/features/home/ui/stats-home.tsx | 16 +-- .../src/features/home/ui/title-home.tsx | 10 +- apps/webapp/src/page-components/home/index.ts | 2 +- apps/webapp/src/widget/footer/index.ts | 1 + apps/webapp/src/widget/footer/ui/footer.tsx | 29 ++++++ apps/webapp/src/widget/index.ts | 1 + 13 files changed, 189 insertions(+), 80 deletions(-) create mode 100644 apps/webapp/src/widget/footer/index.ts create mode 100644 apps/webapp/src/widget/footer/ui/footer.tsx diff --git a/apps/webapp/src/app/layout.tsx b/apps/webapp/src/app/layout.tsx index 30ca10d2..534f5fb9 100644 --- a/apps/webapp/src/app/layout.tsx +++ b/apps/webapp/src/app/layout.tsx @@ -4,7 +4,7 @@ import "./globals.css"; import { Geist, Geist_Mono } from "next/font/google"; import { AppProviders } from "@/providers"; -import { Toast, WalletChangeGuard } from "@/widget"; +import { Footer, Toast, WalletChangeGuard } from "@/widget"; import { RegistrationContextProvider } from "@/entities/registration-process"; import { TooltipProvider } from "@/shared/ui/tooltip"; @@ -46,6 +46,7 @@ export default function RootLayout({ +
); diff --git a/apps/webapp/src/app/page.tsx b/apps/webapp/src/app/page.tsx index 9ea7a17f..3ce992aa 100644 --- a/apps/webapp/src/app/page.tsx +++ b/apps/webapp/src/app/page.tsx @@ -1 +1 @@ -export { HomePage as default } from "@/page-components/home"; \ No newline at end of file +export { HomePage as default } from "@/page-components/home"; diff --git a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts index 0e614176..0bd3efc6 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts @@ -2,7 +2,9 @@ import { useQuery } from "@tanstack/react-query"; -const registeredHostsApiEndpoint = process.env.NEXT_PUBLIC_REGISTERED_HOSTS_API_ENDPOINT || "http://rpc.develop.devnet.shinzo.network:1317/shinzonetwork/host/v1/hosts"; +const registeredHostsApiEndpoint = + process.env.NEXT_PUBLIC_REGISTERED_HOSTS_API_ENDPOINT || + "http://rpc.develop.devnet.shinzo.network:1317/shinzonetwork/host/v1/hosts"; interface RegisteredHost { address: string; diff --git a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts index f72b2ad8..4c83e61f 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts @@ -2,7 +2,9 @@ import { useQuery } from "@tanstack/react-query"; -const registeredIndexerApiEndpoint = process.env.NEXT_PUBLIC_REGISTERED_INDEXER_API_ENDPOINT || "http://rpc.develop.devnet.shinzo.network:1317/shinzonetwork/indexer/v1/indexers"; +const registeredIndexerApiEndpoint = + process.env.NEXT_PUBLIC_REGISTERED_INDEXER_API_ENDPOINT || + "http://rpc.develop.devnet.shinzo.network:1317/shinzonetwork/indexer/v1/indexers"; interface RegisteredIndexer { address: string; @@ -18,7 +20,7 @@ type IndexerResponse = { total: number; next_key: string | null; }; -} +}; async function fetchRegisteredIndexers(): Promise { const response = await fetch(registeredIndexerApiEndpoint); diff --git a/apps/webapp/src/features/home/ui/home.tsx b/apps/webapp/src/features/home/ui/home.tsx index 300cf4bd..50f06290 100644 --- a/apps/webapp/src/features/home/ui/home.tsx +++ b/apps/webapp/src/features/home/ui/home.tsx @@ -6,10 +6,10 @@ import { TitleHome } from "./title-home"; export function Home() { return (
- - - - + + + +
); -} \ No newline at end of file +} diff --git a/apps/webapp/src/features/home/ui/hosts-home.tsx b/apps/webapp/src/features/home/ui/hosts-home.tsx index 2e70598c..19977197 100644 --- a/apps/webapp/src/features/home/ui/hosts-home.tsx +++ b/apps/webapp/src/features/home/ui/hosts-home.tsx @@ -2,38 +2,62 @@ import { useRegisteredHosts } from "../hooks/use-registered-hosts"; export function HostsHome() { - const { data: registeredHosts } = useRegisteredHosts(); - const hosts = registeredHosts?.hosts || []; + const { data: registeredHosts } = useRegisteredHosts(); + const hosts = registeredHosts?.hosts || []; return ( -
-
-
-

Registered Hosts

-

INFRASTRUCTURE_LAYER / DATA_AVAILABILITY

-
- -
-
- - - - - - - - - - {hosts.map((host) => ( - - - - - - ))} - -
ADDRESSDIDCONNECTION STRING
{host.address}{host.did}{host.connection_string}
-
-
+
+
+
+

+ Registered Hosts +

+

+ INFRASTRUCTURE LAYER / DATA AVAILABILITY +

+
+ +
+
+ + + + + + + + + + {hosts.map((host) => ( + + + + + + ))} + +
+ ADDRESS + + DID + + CONNECTION STRING +
+ {host.address} + + {host.did} + + {host.connection_string} +
+
+
); -} \ No newline at end of file +} diff --git a/apps/webapp/src/features/home/ui/indexers-home.tsx b/apps/webapp/src/features/home/ui/indexers-home.tsx index d69e3861..03823cc6 100644 --- a/apps/webapp/src/features/home/ui/indexers-home.tsx +++ b/apps/webapp/src/features/home/ui/indexers-home.tsx @@ -1,35 +1,76 @@ +"use client"; + +import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; + export function IndexersHome() { + const { data: registeredIndexers } = useRegisteredIndexers(); + const indexers = registeredIndexers?.indexers || []; + return (
-
+
-

Registered Indexers

-

NETWORK_LAYER / INDEXING_SERVICES

-
- -
-
- - - - - - - - - - - - - - - - - - - -
ADDRESSDIDCONNECTION STRINGSOURCE CHAINCHAIN ID
0x71C...4e31did:eth:0x71...wss://indexer-01.shinzo.ioEthereum Mainnet1
+

+ Registered Indexers +

+

+ NETWORK LAYER / INDEXING SERVICES +

+ +
+
+ + + + + + + + + + + + {indexers.map((indexer) => ( + + + + + + + + ))} + +
+ ADDRESS + + DID + + CONNECTION STRING + + SOURCE CHAIN + + CHAIN ID +
+ {indexer.address} + + {indexer.did} + + {indexer.connection_string} + + {indexer.source_chain} + + {indexer.source_chain_id} +
+
); -} \ No newline at end of file +} diff --git a/apps/webapp/src/features/home/ui/stats-home.tsx b/apps/webapp/src/features/home/ui/stats-home.tsx index 0dbae356..48ad8b85 100644 --- a/apps/webapp/src/features/home/ui/stats-home.tsx +++ b/apps/webapp/src/features/home/ui/stats-home.tsx @@ -13,14 +13,18 @@ export function StatsHome() { return (
-
-
{UI_HOME_HEADER_CONTENT.registered_indexers}
+
+
+ {UI_HOME_HEADER_CONTENT.registered_indexers} +
{totalIndexers}
+
+
+
+ {UI_HOME_HEADER_CONTENT.registered_hosts}
-
-
{UI_HOME_HEADER_CONTENT.registered_hosts}
{totalHosts}
-
+
); -} \ No newline at end of file +} diff --git a/apps/webapp/src/features/home/ui/title-home.tsx b/apps/webapp/src/features/home/ui/title-home.tsx index 4b2f9e8b..3b9721c9 100644 --- a/apps/webapp/src/features/home/ui/title-home.tsx +++ b/apps/webapp/src/features/home/ui/title-home.tsx @@ -7,8 +7,12 @@ export function TitleHome() { Active Network Infrastructure
-

{UI_HOME_HEADER_CONTENT.home.title}

-

{UI_HOME_HEADER_CONTENT.home.description}

+

+ {UI_HOME_HEADER_CONTENT.home.title} +

+

+ {UI_HOME_HEADER_CONTENT.home.description} +

); -} \ No newline at end of file +} diff --git a/apps/webapp/src/page-components/home/index.ts b/apps/webapp/src/page-components/home/index.ts index bc441888..aed3c619 100644 --- a/apps/webapp/src/page-components/home/index.ts +++ b/apps/webapp/src/page-components/home/index.ts @@ -1 +1 @@ -export { HomePage } from "./ui/page"; \ No newline at end of file +export { HomePage } from "./ui/page"; diff --git a/apps/webapp/src/widget/footer/index.ts b/apps/webapp/src/widget/footer/index.ts new file mode 100644 index 00000000..beaba041 --- /dev/null +++ b/apps/webapp/src/widget/footer/index.ts @@ -0,0 +1 @@ +export { Footer } from "./ui/footer"; diff --git a/apps/webapp/src/widget/footer/ui/footer.tsx b/apps/webapp/src/widget/footer/ui/footer.tsx new file mode 100644 index 00000000..a2f85f03 --- /dev/null +++ b/apps/webapp/src/widget/footer/ui/footer.tsx @@ -0,0 +1,29 @@ +export function Footer() { + return ( + + ); +} diff --git a/apps/webapp/src/widget/index.ts b/apps/webapp/src/widget/index.ts index 4ff302fd..7ac58b6a 100644 --- a/apps/webapp/src/widget/index.ts +++ b/apps/webapp/src/widget/index.ts @@ -3,6 +3,7 @@ export * from "./banner"; export * from "./connect-wallet"; export * from "./copy-to-clipboard"; export * from "./disconnect-wallet"; +export * from "./footer"; export * from "./form-header"; export * from "./header"; export * from "./input-field"; From fac12a567d8927f737b827fdb49cc07a63697c94 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Thu, 7 May 2026 17:09:00 -0400 Subject: [PATCH 3/7] adding pagination --- .../home/hooks/use-registered-hosts.ts | 43 ++++++++++++---- .../home/hooks/use-registered-indexers.ts | 39 ++++++++++++--- .../src/features/home/ui/hosts-home.tsx | 49 +++++++++++++++++- .../src/features/home/ui/indexers-home.tsx | 50 ++++++++++++++++++- .../src/features/home/ui/stats-home.tsx | 14 ++++-- 5 files changed, 172 insertions(+), 23 deletions(-) diff --git a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts index 0bd3efc6..890fdc86 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts @@ -12,16 +12,38 @@ interface RegisteredHost { connection_string: string; } +type PaginationResponse = { + total?: string | number; + next_key?: string | null; +}; + +type PaginationParams = { + key?: string; + offset?: number; + limit?: number; + count_total?: boolean; +}; + interface IndexerResponse { hosts: RegisteredHost[]; - pagination: { - total: number; - next_key: string | null; - }; + pagination?: PaginationResponse; } -async function fetchRegisteredHosts(): Promise { - const response = await fetch(registeredHostsApiEndpoint); +async function fetchRegisteredHosts( + pagination: PaginationParams +): Promise { + const params = new URLSearchParams(); + params.set("pagination.limit", String(pagination.limit ?? 10)); + params.set("pagination.offset", String(pagination.offset ?? 0)); + + if (pagination.key) params.set("pagination.key", pagination.key); + if (pagination.count_total !== undefined) { + params.set("pagination.count_total", String(pagination.count_total)); + } + + const response = await fetch( + `${registeredHostsApiEndpoint}?${params.toString()}` + ); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data: IndexerResponse = await response.json(); @@ -29,10 +51,13 @@ async function fetchRegisteredHosts(): Promise { return data; } -export function useRegisteredHosts(intervalMs = 5000) { +export function useRegisteredHosts( + pagination: PaginationParams = {}, + intervalMs = 30000 +) { return useQuery({ - queryKey: ["registered-hosts"], - queryFn: fetchRegisteredHosts, + queryKey: ["registered-hosts", pagination], + queryFn: () => fetchRegisteredHosts(pagination), refetchInterval: intervalMs, refetchIntervalInBackground: true, }); diff --git a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts index 4c83e61f..99a68d25 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts @@ -16,14 +16,34 @@ interface RegisteredIndexer { type IndexerResponse = { indexers: RegisteredIndexer[]; - pagination: { - total: number; - next_key: string | null; + pagination?: { + total?: string | number; + next_key?: string | null; }; }; -async function fetchRegisteredIndexers(): Promise { - const response = await fetch(registeredIndexerApiEndpoint); +type PaginationParams = { + key?: string; + offset?: number; + limit?: number; + count_total?: boolean; +}; + +async function fetchRegisteredIndexers( + pagination: PaginationParams +): Promise { + const params = new URLSearchParams(); + params.set("pagination.limit", String(pagination.limit ?? 10)); + params.set("pagination.offset", String(pagination.offset ?? 0)); + + if (pagination.key) params.set("pagination.key", pagination.key); + if (pagination.count_total !== undefined) { + params.set("pagination.count_total", String(pagination.count_total)); + } + + const response = await fetch( + `${registeredIndexerApiEndpoint}?${params.toString()}` + ); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data: IndexerResponse = await response.json(); @@ -31,10 +51,13 @@ async function fetchRegisteredIndexers(): Promise { return data; } -export function useRegisteredIndexers(intervalMs = 5000) { +export function useRegisteredIndexers( + pagination: PaginationParams = {}, + intervalMs = 30000 +) { return useQuery({ - queryKey: ["registered-indexers"], - queryFn: fetchRegisteredIndexers, + queryKey: ["registered-indexers", pagination], + queryFn: () => fetchRegisteredIndexers(pagination), refetchInterval: intervalMs, refetchIntervalInBackground: true, }); diff --git a/apps/webapp/src/features/home/ui/hosts-home.tsx b/apps/webapp/src/features/home/ui/hosts-home.tsx index 19977197..355a2790 100644 --- a/apps/webapp/src/features/home/ui/hosts-home.tsx +++ b/apps/webapp/src/features/home/ui/hosts-home.tsx @@ -1,9 +1,27 @@ "use client"; import { useRegisteredHosts } from "../hooks/use-registered-hosts"; +import { useRouter } from "next/navigation"; +import { useMemo, useState } from "react"; export function HostsHome() { - const { data: registeredHosts } = useRegisteredHosts(); + const PAGE_SIZE = 5; + const [offset, setOffset] = useState(0); + const queryParams = useMemo( + () => ({ offset, limit: PAGE_SIZE, count_total: true }), + [offset] + ); + const { data: registeredHosts, isFetching } = useRegisteredHosts(queryParams); const hosts = registeredHosts?.hosts || []; + const total = Number(registeredHosts?.pagination?.total ?? 0); + const nextKey = registeredHosts?.pagination?.next_key; + const hasNextPage = Boolean(nextKey) || offset + hosts.length < total; + const hasPrevPage = offset > 0; + const router = useRouter(); + const handleRegisterAsHost = () => { + router.push("/host-registration"); + }; + const currentPage = Math.floor(offset / PAGE_SIZE) + 1; + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); return (
@@ -19,6 +37,7 @@ export function HostsHome() { @@ -58,6 +77,34 @@ export function HostsHome() {
+
+

+ {total > 0 + ? `Showing ${offset + 1}-${offset + hosts.length} of ${total}` + : "No hosts found"} +

+
+ + + Page {currentPage} / {totalPages} + + +
+
); } diff --git a/apps/webapp/src/features/home/ui/indexers-home.tsx b/apps/webapp/src/features/home/ui/indexers-home.tsx index 03823cc6..1b730cca 100644 --- a/apps/webapp/src/features/home/ui/indexers-home.tsx +++ b/apps/webapp/src/features/home/ui/indexers-home.tsx @@ -1,10 +1,29 @@ "use client"; import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; +import { useRouter } from "next/navigation"; +import { useMemo, useState } from "react"; export function IndexersHome() { - const { data: registeredIndexers } = useRegisteredIndexers(); + const PAGE_SIZE = 5; + const [offset, setOffset] = useState(0); + const queryParams = useMemo( + () => ({ offset, limit: PAGE_SIZE, count_total: true }), + [offset] + ); + const { data: registeredIndexers, isFetching } = + useRegisteredIndexers(queryParams); const indexers = registeredIndexers?.indexers || []; + const total = Number(registeredIndexers?.pagination?.total ?? 0); + const nextKey = registeredIndexers?.pagination?.next_key; + const hasNextPage = Boolean(nextKey) || offset + indexers.length < total; + const hasPrevPage = offset > 0; + const router = useRouter(); + const handleRegisterAsIndexer = () => { + router.push("/indexer-registration"); + }; + const currentPage = Math.floor(offset / PAGE_SIZE) + 1; + const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); return (
@@ -20,6 +39,7 @@ export function IndexersHome() { @@ -71,6 +91,34 @@ export function IndexersHome() {
+
+

+ {total > 0 + ? `Showing ${offset + 1}-${offset + indexers.length} of ${total}` + : "No indexers found"} +

+
+ + + Page {currentPage} / {totalPages} + + +
+
); } diff --git a/apps/webapp/src/features/home/ui/stats-home.tsx b/apps/webapp/src/features/home/ui/stats-home.tsx index 48ad8b85..0b983a3c 100644 --- a/apps/webapp/src/features/home/ui/stats-home.tsx +++ b/apps/webapp/src/features/home/ui/stats-home.tsx @@ -5,11 +5,17 @@ import { useRegisteredHosts } from "../hooks/use-registered-hosts"; import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; export function StatsHome() { - const { data: registeredIndexers } = useRegisteredIndexers(); - const { data: registeredHosts } = useRegisteredHosts(); + const { data: registeredIndexers } = useRegisteredIndexers({ + limit: 1, + count_total: true, + }); + const { data: registeredHosts } = useRegisteredHosts({ + limit: 1, + count_total: true, + }); - const totalIndexers = registeredIndexers?.pagination.total || 0; - const totalHosts = registeredHosts?.pagination.total || 0; + const totalIndexers = Number(registeredIndexers?.pagination?.total ?? 0); + const totalHosts = Number(registeredHosts?.pagination?.total ?? 0); return (
From 86ac3493f9492f5476e3dc98c693b7448ac92c09 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Wed, 20 May 2026 17:12:29 -0400 Subject: [PATCH 4/7] reusing the tables --- apps/webapp/src/app/globals.css | 2 + apps/webapp/src/features/home/ui/home.tsx | 2 +- .../src/features/home/ui/hosts-home.tsx | 114 ++++++-------- .../src/features/home/ui/indexers-home.tsx | 142 ++++++++---------- .../src/features/home/ui/stats-home.tsx | 4 +- 5 files changed, 113 insertions(+), 151 deletions(-) diff --git a/apps/webapp/src/app/globals.css b/apps/webapp/src/app/globals.css index a875f78a..2bdd5f00 100644 --- a/apps/webapp/src/app/globals.css +++ b/apps/webapp/src/app/globals.css @@ -13,6 +13,7 @@ --color-ui-warning-foreground: #ffffff; --color-ui-destructive: #d32c34; --color-ui-destructive-foreground: #ffffff; + --color-ui-bg-accent-light: #fffbfb; } @theme inline { @@ -26,6 +27,7 @@ --color-muted-foreground: var(--color-ui-text-muted); --color-accent: var(--color-ui-accent); --color-accent-foreground: var(--color-ui-text-accent); + --color-background-accent-light: var(--color-ui-bg-accent-light); --color-primary: var(--color-ui-primary); --color-primary-foreground: var(--color-ui-primary-foreground); diff --git a/apps/webapp/src/features/home/ui/home.tsx b/apps/webapp/src/features/home/ui/home.tsx index 50f06290..85bd4dc1 100644 --- a/apps/webapp/src/features/home/ui/home.tsx +++ b/apps/webapp/src/features/home/ui/home.tsx @@ -5,7 +5,7 @@ import { TitleHome } from "./title-home"; export function Home() { return ( -
+
diff --git a/apps/webapp/src/features/home/ui/hosts-home.tsx b/apps/webapp/src/features/home/ui/hosts-home.tsx index 355a2790..851e6a3a 100644 --- a/apps/webapp/src/features/home/ui/hosts-home.tsx +++ b/apps/webapp/src/features/home/ui/hosts-home.tsx @@ -1,7 +1,9 @@ "use client"; +import { TableLayout, TableNullableCell } from "@shinzo/ui/table"; import { useRegisteredHosts } from "../hooks/use-registered-hosts"; import { useRouter } from "next/navigation"; import { useMemo, useState } from "react"; +import { DEFAULT_LIMIT, Pagination } from "@shinzo/ui/pagination"; export function HostsHome() { const PAGE_SIZE = 5; @@ -13,19 +15,17 @@ export function HostsHome() { const { data: registeredHosts, isFetching } = useRegisteredHosts(queryParams); const hosts = registeredHosts?.hosts || []; const total = Number(registeredHosts?.pagination?.total ?? 0); - const nextKey = registeredHosts?.pagination?.next_key; - const hasNextPage = Boolean(nextKey) || offset + hosts.length < total; - const hasPrevPage = offset > 0; const router = useRouter(); const handleRegisterAsHost = () => { router.push("/host-registration"); }; const currentPage = Math.floor(offset / PAGE_SIZE) + 1; - const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + + const tableHeadings = ["Address", "DID", "Connection String"]; return ( -
-
+
+

Registered Hosts @@ -42,67 +42,49 @@ export function HostsHome() { Register as Host

-
- - - - - - - - - - {hosts.map((host) => ( - + 0 ? tableHeadings : [""]} + gridClass={ + hosts.length > 0 ? "grid-cols[repeat(3,1fr)]" : "grid-cols-1" + } + iterable={hosts ?? []} + rowRenderer={(host) => ( + <> + + {(value) => ( + {value} + )} + + + + {(value) => ( + {value} + )} + + + - - - - - ))} - -
- ADDRESS - - DID - - CONNECTION STRING -
- {host.address} - - {host.did} - - {host.connection_string} -
-
-
-

- {total > 0 - ? `Showing ${offset + 1}-${offset + hosts.length} of ${total}` - : "No hosts found"} -

-
- - - Page {currentPage} / {totalPages} - - + {(value) => ( + + {value} + + )} + + + )} + /> +
+
diff --git a/apps/webapp/src/features/home/ui/indexers-home.tsx b/apps/webapp/src/features/home/ui/indexers-home.tsx index 1b730cca..2e471620 100644 --- a/apps/webapp/src/features/home/ui/indexers-home.tsx +++ b/apps/webapp/src/features/home/ui/indexers-home.tsx @@ -3,31 +3,34 @@ import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; import { useRouter } from "next/navigation"; import { useMemo, useState } from "react"; +import { TableLayout, TableNullableCell } from "@shinzo/ui/table"; +import { Pagination } from "@shinzo/ui/pagination"; export function IndexersHome() { - const PAGE_SIZE = 5; + const DEFAULT_LIMIT = 5; const [offset, setOffset] = useState(0); const queryParams = useMemo( - () => ({ offset, limit: PAGE_SIZE, count_total: true }), + () => ({ offset, limit: DEFAULT_LIMIT, count_total: true }), [offset] ); const { data: registeredIndexers, isFetching } = useRegisteredIndexers(queryParams); const indexers = registeredIndexers?.indexers || []; const total = Number(registeredIndexers?.pagination?.total ?? 0); - const nextKey = registeredIndexers?.pagination?.next_key; - const hasNextPage = Boolean(nextKey) || offset + indexers.length < total; - const hasPrevPage = offset > 0; + // const nextKey = registeredIndexers?.pagination?.next_key; + // const hasNextPage = Boolean(nextKey) || offset + indexers.length < total; + // const hasPrevPage = offset > 0; const router = useRouter(); const handleRegisterAsIndexer = () => { router.push("/indexer-registration"); }; - const currentPage = Math.floor(offset / PAGE_SIZE) + 1; - const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); + const currentPage = Math.floor(offset / DEFAULT_LIMIT) + 1; + // const totalPages = Math.max(1, Math.ceil(total / DEFAULT_LIMIT)); + const tableHeadings = ["Address", "DID", "Chain", "Connection String"]; return ( -
-
+
+

Registered Indexers @@ -44,79 +47,54 @@ export function IndexersHome() { REGISTER AS INDEXER

-
- - - - - - - - - - - - {indexers.map((indexer) => ( - - - - - - - - ))} - -
- ADDRESS - - DID - - CONNECTION STRING - - SOURCE CHAIN - - CHAIN ID -
- {indexer.address} - - {indexer.did} - - {indexer.connection_string} - - {indexer.source_chain} - - {indexer.source_chain_id} -
-
-
-

- {total > 0 - ? `Showing ${offset + 1}-${offset + indexers.length} of ${total}` - : "No indexers found"} -

-
- - - Page {currentPage} / {totalPages} - - +
+ 0 ? tableHeadings : [""]} + gridClass={ + indexers.length > 0 ? "grid-cols[repeat(4,1fr)]" : "grid-cols-1" + } + iterable={indexers ?? []} + rowRenderer={(indexer) => ( + <> + + {(value) => ( + {value} + )} + + + + {(value) => ( + {value} + )} + + + + {(value) => ( + + {value.charAt(0).toUpperCase() + value.slice(1)} + + )} + + + + {(value) => ( + + {value} + + )} + + + )} + /> +
+
diff --git a/apps/webapp/src/features/home/ui/stats-home.tsx b/apps/webapp/src/features/home/ui/stats-home.tsx index 0b983a3c..02354b43 100644 --- a/apps/webapp/src/features/home/ui/stats-home.tsx +++ b/apps/webapp/src/features/home/ui/stats-home.tsx @@ -18,8 +18,8 @@ export function StatsHome() { const totalHosts = Number(registeredHosts?.pagination?.total ?? 0); return ( -
-
+
+
{UI_HOME_HEADER_CONTENT.registered_indexers}
From c496bfebaeffeb604f6aaf20ad2341e7b3c7c359 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Thu, 21 May 2026 00:15:58 -0400 Subject: [PATCH 5/7] fixing the page load glitch --- .../home/hooks/use-registered-hosts.ts | 1 + .../home/hooks/use-registered-indexers.ts | 1 + .../src/features/home/ui/hosts-home.tsx | 8 ++--- .../src/features/home/ui/indexers-home.tsx | 8 ++--- .../src/features/home/ui/stats-home.tsx | 35 ++++++++++++------- .../src/providers/ui/query-provider.tsx | 9 ++++- 6 files changed, 39 insertions(+), 23 deletions(-) diff --git a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts index 890fdc86..2574327c 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts @@ -60,5 +60,6 @@ export function useRegisteredHosts( queryFn: () => fetchRegisteredHosts(pagination), refetchInterval: intervalMs, refetchIntervalInBackground: true, + placeholderData: (previousData) => previousData, }); } diff --git a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts index 99a68d25..a2d189ad 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts @@ -60,5 +60,6 @@ export function useRegisteredIndexers( queryFn: () => fetchRegisteredIndexers(pagination), refetchInterval: intervalMs, refetchIntervalInBackground: true, + placeholderData: (previousData) => previousData, }); } diff --git a/apps/webapp/src/features/home/ui/hosts-home.tsx b/apps/webapp/src/features/home/ui/hosts-home.tsx index 851e6a3a..0cedacc6 100644 --- a/apps/webapp/src/features/home/ui/hosts-home.tsx +++ b/apps/webapp/src/features/home/ui/hosts-home.tsx @@ -12,7 +12,7 @@ export function HostsHome() { () => ({ offset, limit: PAGE_SIZE, count_total: true }), [offset] ); - const { data: registeredHosts, isFetching } = useRegisteredHosts(queryParams); + const { data: registeredHosts, isPending } = useRegisteredHosts(queryParams); const hosts = registeredHosts?.hosts || []; const total = Number(registeredHosts?.pagination?.total ?? 0); const router = useRouter(); @@ -44,13 +44,11 @@ export function HostsHome() {
0 ? tableHeadings : [""]} - gridClass={ - hosts.length > 0 ? "grid-cols[repeat(3,1fr)]" : "grid-cols-1" - } + gridClass="grid-cols[repeat(3,1fr)]" iterable={hosts ?? []} rowRenderer={(host) => ( <> diff --git a/apps/webapp/src/features/home/ui/indexers-home.tsx b/apps/webapp/src/features/home/ui/indexers-home.tsx index 2e471620..19f43c1f 100644 --- a/apps/webapp/src/features/home/ui/indexers-home.tsx +++ b/apps/webapp/src/features/home/ui/indexers-home.tsx @@ -13,7 +13,7 @@ export function IndexersHome() { () => ({ offset, limit: DEFAULT_LIMIT, count_total: true }), [offset] ); - const { data: registeredIndexers, isFetching } = + const { data: registeredIndexers, isPending } = useRegisteredIndexers(queryParams); const indexers = registeredIndexers?.indexers || []; const total = Number(registeredIndexers?.pagination?.total ?? 0); @@ -49,13 +49,11 @@ export function IndexersHome() {
0 ? tableHeadings : [""]} - gridClass={ - indexers.length > 0 ? "grid-cols[repeat(4,1fr)]" : "grid-cols-1" - } + gridClass="grid-cols[repeat(4,1fr)]" iterable={indexers ?? []} rowRenderer={(indexer) => ( <> diff --git a/apps/webapp/src/features/home/ui/stats-home.tsx b/apps/webapp/src/features/home/ui/stats-home.tsx index 02354b43..c72295ba 100644 --- a/apps/webapp/src/features/home/ui/stats-home.tsx +++ b/apps/webapp/src/features/home/ui/stats-home.tsx @@ -5,17 +5,24 @@ import { useRegisteredHosts } from "../hooks/use-registered-hosts"; import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; export function StatsHome() { - const { data: registeredIndexers } = useRegisteredIndexers({ - limit: 1, - count_total: true, - }); - const { data: registeredHosts } = useRegisteredHosts({ - limit: 1, - count_total: true, - }); + const { data: registeredIndexers, isPending: indexersPending } = + useRegisteredIndexers({ + limit: 1, + count_total: true, + }); + const { data: registeredHosts, isPending: hostsPending } = useRegisteredHosts( + { + limit: 1, + count_total: true, + } + ); - const totalIndexers = Number(registeredIndexers?.pagination?.total ?? 0); - const totalHosts = Number(registeredHosts?.pagination?.total ?? 0); + const totalIndexers = indexersPending + ? null + : Number(registeredIndexers?.pagination?.total ?? 0); + const totalHosts = hostsPending + ? null + : Number(registeredHosts?.pagination?.total ?? 0); return (
@@ -23,13 +30,17 @@ export function StatsHome() {
{UI_HOME_HEADER_CONTENT.registered_indexers}
-
{totalIndexers}
+
+ {totalIndexers ?? "—"} +
{UI_HOME_HEADER_CONTENT.registered_hosts}
-
{totalHosts}
+
+ {totalHosts ?? "—"} +
); diff --git a/apps/webapp/src/providers/ui/query-provider.tsx b/apps/webapp/src/providers/ui/query-provider.tsx index c4c8ccd5..b06b77a8 100644 --- a/apps/webapp/src/providers/ui/query-provider.tsx +++ b/apps/webapp/src/providers/ui/query-provider.tsx @@ -3,7 +3,14 @@ import type { ReactNode } from "react"; import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + refetchOnWindowFocus: false, + }, + }, +}); interface AppQueryProviderProps { children: ReactNode; From 4a56eee617458221d5c080e40fbe56cf714d8182 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Thu, 21 May 2026 16:30:17 -0400 Subject: [PATCH 6/7] reuse pagination --- .../home/hooks/use-registered-hosts.ts | 4 + .../home/hooks/use-registered-indexers.ts | 4 + .../src/features/home/ui/hosts-home.tsx | 72 +++++++++++----- .../src/features/home/ui/indexers-home.tsx | 85 +++++++++++++------ packages/ui/src/pagination/pagination.tsx | 12 ++- 5 files changed, 128 insertions(+), 49 deletions(-) diff --git a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts index 2574327c..730ffe84 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts @@ -22,6 +22,7 @@ type PaginationParams = { offset?: number; limit?: number; count_total?: boolean; + reverse?: boolean; }; interface IndexerResponse { @@ -40,6 +41,9 @@ async function fetchRegisteredHosts( if (pagination.count_total !== undefined) { params.set("pagination.count_total", String(pagination.count_total)); } + if (pagination.reverse !== undefined) { + params.set("pagination.reverse", String(pagination.reverse)); + } const response = await fetch( `${registeredHostsApiEndpoint}?${params.toString()}` diff --git a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts index a2d189ad..a02121dc 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts @@ -27,6 +27,7 @@ type PaginationParams = { offset?: number; limit?: number; count_total?: boolean; + reverse?: boolean; }; async function fetchRegisteredIndexers( @@ -40,6 +41,9 @@ async function fetchRegisteredIndexers( if (pagination.count_total !== undefined) { params.set("pagination.count_total", String(pagination.count_total)); } + if (pagination.reverse !== undefined) { + params.set("pagination.reverse", String(pagination.reverse)); + } const response = await fetch( `${registeredIndexerApiEndpoint}?${params.toString()}` diff --git a/apps/webapp/src/features/home/ui/hosts-home.tsx b/apps/webapp/src/features/home/ui/hosts-home.tsx index 0cedacc6..32f918ba 100644 --- a/apps/webapp/src/features/home/ui/hosts-home.tsx +++ b/apps/webapp/src/features/home/ui/hosts-home.tsx @@ -1,27 +1,48 @@ "use client"; + +import { Suspense, useMemo } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; import { TableLayout, TableNullableCell } from "@shinzo/ui/table"; +import { + getServerPage, + Pagination, + DEFAULT_LIMIT, + type PageParams, +} from "@shinzo/ui/pagination"; import { useRegisteredHosts } from "../hooks/use-registered-hosts"; -import { useRouter } from "next/navigation"; -import { useMemo, useState } from "react"; -import { DEFAULT_LIMIT, Pagination } from "@shinzo/ui/pagination"; -export function HostsHome() { - const PAGE_SIZE = 5; - const [offset, setOffset] = useState(0); +const HOSTS_PAGE_PARAM = "hostsPage"; + +const tableHeadings = ["Address", "DID", "Connection String"]; + +function HostsHomeContent() { + const searchParams = useSearchParams(); + const router = useRouter(); + + const { page, offset, limit }: PageParams = useMemo( + () => + getServerPage({ + page: searchParams.get(HOSTS_PAGE_PARAM) ?? "1", + limit: String(DEFAULT_LIMIT), + }), + [searchParams] + ); + const queryParams = useMemo( - () => ({ offset, limit: PAGE_SIZE, count_total: true }), - [offset] + () => ({ + offset, + limit, + count_total: true, + }), + [offset, limit] ); + const { data: registeredHosts, isPending } = useRegisteredHosts(queryParams); - const hosts = registeredHosts?.hosts || []; + const hosts = registeredHosts?.hosts ?? []; const total = Number(registeredHosts?.pagination?.total ?? 0); - const router = useRouter(); const handleRegisterAsHost = () => { router.push("/host-registration"); }; - const currentPage = Math.floor(offset / PAGE_SIZE) + 1; - - const tableHeadings = ["Address", "DID", "Connection String"]; return (
@@ -77,14 +98,25 @@ export function HostsHome() { )} /> -
- -
+ {total > DEFAULT_LIMIT && ( +
+ +
+ )}
); } + +export function HostsHome() { + return ( + + + + ); +} diff --git a/apps/webapp/src/features/home/ui/indexers-home.tsx b/apps/webapp/src/features/home/ui/indexers-home.tsx index 19f43c1f..37e345b1 100644 --- a/apps/webapp/src/features/home/ui/indexers-home.tsx +++ b/apps/webapp/src/features/home/ui/indexers-home.tsx @@ -1,33 +1,50 @@ "use client"; -import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; -import { useRouter } from "next/navigation"; -import { useMemo, useState } from "react"; +import { Suspense, useMemo } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; import { TableLayout, TableNullableCell } from "@shinzo/ui/table"; -import { Pagination } from "@shinzo/ui/pagination"; +import { + getServerPage, + Pagination, + DEFAULT_LIMIT, + type PageParams, +} from "@shinzo/ui/pagination"; +import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; + +const INDEXERS_PAGE_PARAM = "indexersPage"; + +const tableHeadings = ["Address", "DID", "Chain", "Connection String"]; + +function IndexersHomeContent() { + const searchParams = useSearchParams(); + const router = useRouter(); + + const { page, offset, limit }: PageParams = useMemo( + () => + getServerPage({ + page: searchParams.get(INDEXERS_PAGE_PARAM) ?? "1", + limit: String(DEFAULT_LIMIT), + }), + [searchParams] + ); -export function IndexersHome() { - const DEFAULT_LIMIT = 5; - const [offset, setOffset] = useState(0); const queryParams = useMemo( - () => ({ offset, limit: DEFAULT_LIMIT, count_total: true }), - [offset] + () => ({ + offset, + limit, + count_total: true, + }), + [offset, limit] ); const { data: registeredIndexers, isPending } = useRegisteredIndexers(queryParams); - const indexers = registeredIndexers?.indexers || []; + const indexers = registeredIndexers?.indexers ?? []; const total = Number(registeredIndexers?.pagination?.total ?? 0); - // const nextKey = registeredIndexers?.pagination?.next_key; - // const hasNextPage = Boolean(nextKey) || offset + indexers.length < total; - // const hasPrevPage = offset > 0; - const router = useRouter(); + const handleRegisterAsIndexer = () => { router.push("/indexer-registration"); }; - const currentPage = Math.floor(offset / DEFAULT_LIMIT) + 1; - // const totalPages = Math.max(1, Math.ceil(total / DEFAULT_LIMIT)); - const tableHeadings = ["Address", "DID", "Chain", "Connection String"]; return (
@@ -63,13 +80,13 @@ export function IndexersHome() { )} - + {(value) => ( {value} )} - + {(value) => ( {value.charAt(0).toUpperCase() + value.slice(1)} @@ -77,7 +94,10 @@ export function IndexersHome() { )} - + {(value) => ( {value} @@ -87,14 +107,25 @@ export function IndexersHome() { )} /> -
- -
+ {total > DEFAULT_LIMIT && ( +
+ +
+ )}
); } + +export function IndexersHome() { + return ( + + + + ); +} diff --git a/packages/ui/src/pagination/pagination.tsx b/packages/ui/src/pagination/pagination.tsx index 2c933e5b..b07038f9 100644 --- a/packages/ui/src/pagination/pagination.tsx +++ b/packages/ui/src/pagination/pagination.tsx @@ -16,9 +16,17 @@ export interface PaginationProps extends ComponentProps<"nav"> { itemsPerPage?: number; totalItems?: number; page: number; + /** Query param name for page number (default `page`). Use distinct names when multiple paginated sections share one route. */ + pageParam?: string; } -export const Pagination = ({ page, itemsPerPage = DEFAULT_LIMIT, totalItems = 0, ...props }: PaginationProps) => { +export const Pagination = ({ + page, + itemsPerPage = DEFAULT_LIMIT, + totalItems = 0, + pageParam = "page", + ...props +}: PaginationProps) => { const totalPages = Math.max(1, Math.ceil(totalItems / itemsPerPage)); const pathname = usePathname(); @@ -26,7 +34,7 @@ export const Pagination = ({ page, itemsPerPage = DEFAULT_LIMIT, totalItems = 0, const getPageLink = (pageNum: number) => { const newParams = new URLSearchParams(params.toString()); - newParams.set('page', pageNum.toString()); + newParams.set(pageParam, pageNum.toString()); const joiner = pathname.includes('?') ? '&' : '?'; return `${pathname}${joiner}${newParams.toString()}`; }; From d985e6fe60602b53c4892103f9868089c3f5ca23 Mon Sep 17 00:00:00 2001 From: Niranjana Binoy <43930900+NiranjanaBinoy@users.noreply.github.com> Date: Fri, 22 May 2026 16:23:47 -0400 Subject: [PATCH 7/7] updating cursor based pagination while sticking to existing ui style --- .../home/hooks/use-cursor-page-pagination.ts | 148 ++++++++++++++++++ .../home/hooks/use-registered-hosts.ts | 50 ++---- .../home/hooks/use-registered-indexers.ts | 48 ++---- .../src/features/home/lib/pagination.ts | 38 +++++ .../src/features/home/ui/hosts-home.tsx | 59 ++++--- .../src/features/home/ui/indexers-home.tsx | 57 +++---- 6 files changed, 271 insertions(+), 129 deletions(-) create mode 100644 apps/webapp/src/features/home/hooks/use-cursor-page-pagination.ts create mode 100644 apps/webapp/src/features/home/lib/pagination.ts diff --git a/apps/webapp/src/features/home/hooks/use-cursor-page-pagination.ts b/apps/webapp/src/features/home/hooks/use-cursor-page-pagination.ts new file mode 100644 index 00000000..d140de2d --- /dev/null +++ b/apps/webapp/src/features/home/hooks/use-cursor-page-pagination.ts @@ -0,0 +1,148 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { getServerPage, type PageParams } from "@shinzo/ui/pagination"; +import { + hasNextCursorPage, + type CursorPaginationParams, +} from "../lib/pagination"; + +type CursorKeyStorage = (string | undefined)[]; + +function readFromCursorKeyStorage(storageKey: string): CursorKeyStorage { + if (typeof sessionStorage === "undefined") { + return [undefined]; + } + try { + const raw = sessionStorage.getItem(storageKey); + if (!raw) return [undefined]; + const parsed = JSON.parse(raw) as CursorKeyStorage; + return Array.isArray(parsed) && parsed.length > 0 ? parsed : [undefined]; + } catch { + return [undefined]; + } +} + +function writeToCursorKeyStorage( + storageKey: string, + storedKeys: CursorKeyStorage +) { + if (typeof sessionStorage === "undefined") return; + sessionStorage.setItem(storageKey, JSON.stringify(storedKeys)); +} + +type UseCursorPagePaginationOptions = { + pageParam: string; + storageKey: string; + limit: number; +}; + +/** + * Maps URL page numbers to Cosmos cursor keys (`pagination.key` / `next_key`). + * Page 1 uses no key; page N uses the `next_key` saved when page Nāˆ’1 was fetched. + */ +export function useCursorPagePagination({ + pageParam, + storageKey, + limit, +}: UseCursorPagePaginationOptions) { + const searchParams = useSearchParams(); + const router = useRouter(); + const [storedKeys, setStoredKeys] = useState(() => + readFromCursorKeyStorage(storageKey) + ); + + const { page, offset }: PageParams = useMemo( + () => + getServerPage({ + page: searchParams.get(pageParam) ?? "1", + limit: String(limit), + }), + [searchParams, pageParam, limit] + ); + + /** api is returning `total: 0` on keyed pages when tested; keep the count from page 1. */ + const [totalItems, setTotalItems] = useState(0); + + const totalPages = useMemo( + () => Math.max(1, Math.ceil(totalItems / limit)), + [totalItems, limit] + ); + + const queryParams: CursorPaginationParams = useMemo(() => { + if (page <= 1) { + return { limit, count_total: true }; + } + + const cursorKey = storedKeys[page - 1]; + if (cursorKey) { + return { key: cursorKey, limit, count_total: true }; + } + + // jump to last page: fall back to offset when cursor for this page was never visited + if (page === totalPages && totalPages > 1) { + return { offset: (page - 1) * limit, limit, count_total: true }; + } + + return { limit, count_total: true }; + }, [page, storedKeys, limit, totalPages]); + + const applyPaginationData = useCallback( + (nextKey: string | null | undefined, total: number) => { + if (total > 0) { + setTotalItems(total); + } + const effectiveTotal = total > 0 ? total : totalItems; + const pageCount = Math.max(1, Math.ceil(effectiveTotal / limit)); + + if (effectiveTotal > 0 && page > pageCount) { + const params = new URLSearchParams(searchParams.toString()); + params.set(pageParam, String(pageCount)); + router.replace(`?${params.toString()}`); + return; + } + + setStoredKeys((prev) => { + const next = [...prev]; + next[page] = nextKey ?? undefined; + writeToCursorKeyStorage(storageKey, next); + return next; + }); + }, + [page, limit, pageParam, router, searchParams, storageKey, totalItems] + ); + + const resetToFirstPage = useCallback(() => { + const resetStoredKeys: CursorKeyStorage = [undefined]; + setTotalItems(0); + setStoredKeys(resetStoredKeys); + writeToCursorKeyStorage(storageKey, resetStoredKeys); + const params = new URLSearchParams(searchParams.toString()); + params.set(pageParam, "1"); + router.replace(`?${params.toString()}`); + }, [pageParam, router, searchParams, storageKey]); + + useEffect(() => { + if (page > 1 && !storedKeys[page - 1] && page !== totalPages) { + resetToFirstPage(); + } + }, [page, storedKeys, resetToFirstPage, totalPages]); + + const canGoNext = (nextKey: string | null | undefined, total: number) => { + const effectiveTotal = total > 0 ? total : totalItems; + const totalPages = Math.max(1, Math.ceil(effectiveTotal / limit)); + return page < totalPages && hasNextCursorPage(nextKey); + }; + + return { + page, + offset, + limit, + queryParams, + applyPaginationData, + canGoNext, + resetToFirstPage, + totalItems, + }; +} diff --git a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts index 730ffe84..91ecc2aa 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-hosts.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-hosts.ts @@ -1,62 +1,44 @@ "use client"; import { useQuery } from "@tanstack/react-query"; +import { + buildCursorPaginationSearchParams, + type CursorPaginationParams, + type CursorPaginationResponse, +} from "../lib/pagination"; const registeredHostsApiEndpoint = process.env.NEXT_PUBLIC_REGISTERED_HOSTS_API_ENDPOINT || "http://rpc.develop.devnet.shinzo.network:1317/shinzonetwork/host/v1/hosts"; -interface RegisteredHost { +export type RegisteredHost = { address: string; did: string; connection_string: string; -} - -type PaginationResponse = { - total?: string | number; - next_key?: string | null; -}; - -type PaginationParams = { - key?: string; - offset?: number; - limit?: number; - count_total?: boolean; - reverse?: boolean; }; -interface IndexerResponse { +export type RegisteredHostsResponse = { hosts: RegisteredHost[]; - pagination?: PaginationResponse; -} + pagination?: CursorPaginationResponse; +}; -async function fetchRegisteredHosts( - pagination: PaginationParams -): Promise { - const params = new URLSearchParams(); - params.set("pagination.limit", String(pagination.limit ?? 10)); - params.set("pagination.offset", String(pagination.offset ?? 0)); +export type RegisteredHostsPaginationParams = CursorPaginationParams; - if (pagination.key) params.set("pagination.key", pagination.key); - if (pagination.count_total !== undefined) { - params.set("pagination.count_total", String(pagination.count_total)); - } - if (pagination.reverse !== undefined) { - params.set("pagination.reverse", String(pagination.reverse)); - } +export async function fetchRegisteredHosts( + pagination: RegisteredHostsPaginationParams +): Promise { + const params = buildCursorPaginationSearchParams(pagination); const response = await fetch( `${registeredHostsApiEndpoint}?${params.toString()}` ); if (!response.ok) throw new Error(`HTTP ${response.status}`); - const data: IndexerResponse = await response.json(); - - return data; + return response.json() as Promise; } export function useRegisteredHosts( - pagination: PaginationParams = {}, + pagination: RegisteredHostsPaginationParams, intervalMs = 30000 ) { return useQuery({ diff --git a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts index a02121dc..38d5cca3 100644 --- a/apps/webapp/src/features/home/hooks/use-registered-indexers.ts +++ b/apps/webapp/src/features/home/hooks/use-registered-indexers.ts @@ -1,62 +1,46 @@ "use client"; import { useQuery } from "@tanstack/react-query"; +import { + buildCursorPaginationSearchParams, + CursorPaginationResponse, + type CursorPaginationParams, +} from "../lib/pagination"; const registeredIndexerApiEndpoint = process.env.NEXT_PUBLIC_REGISTERED_INDEXER_API_ENDPOINT || "http://rpc.develop.devnet.shinzo.network:1317/shinzonetwork/indexer/v1/indexers"; -interface RegisteredIndexer { +export type RegisteredIndexer = { address: string; did: string; connection_string: string; source_chain: string; source_chain_id: string; -} - -type IndexerResponse = { - indexers: RegisteredIndexer[]; - pagination?: { - total?: string | number; - next_key?: string | null; - }; }; -type PaginationParams = { - key?: string; - offset?: number; - limit?: number; - count_total?: boolean; - reverse?: boolean; +export type RegisteredIndexersResponse = { + indexers: RegisteredIndexer[]; + pagination?: CursorPaginationResponse; }; -async function fetchRegisteredIndexers( - pagination: PaginationParams -): Promise { - const params = new URLSearchParams(); - params.set("pagination.limit", String(pagination.limit ?? 10)); - params.set("pagination.offset", String(pagination.offset ?? 0)); +export type RegisteredIndexersPaginationParams = CursorPaginationParams; - if (pagination.key) params.set("pagination.key", pagination.key); - if (pagination.count_total !== undefined) { - params.set("pagination.count_total", String(pagination.count_total)); - } - if (pagination.reverse !== undefined) { - params.set("pagination.reverse", String(pagination.reverse)); - } +export async function fetchRegisteredIndexers( + pagination: RegisteredIndexersPaginationParams +): Promise { + const params = buildCursorPaginationSearchParams(pagination); const response = await fetch( `${registeredIndexerApiEndpoint}?${params.toString()}` ); if (!response.ok) throw new Error(`HTTP ${response.status}`); - const data: IndexerResponse = await response.json(); - - return data; + return response.json() as Promise; } export function useRegisteredIndexers( - pagination: PaginationParams = {}, + pagination: RegisteredIndexersPaginationParams, intervalMs = 30000 ) { return useQuery({ diff --git a/apps/webapp/src/features/home/lib/pagination.ts b/apps/webapp/src/features/home/lib/pagination.ts new file mode 100644 index 00000000..c46ccef9 --- /dev/null +++ b/apps/webapp/src/features/home/lib/pagination.ts @@ -0,0 +1,38 @@ +export type CursorPaginationResponse = { + total?: string | number; + next_key?: string | null; +}; + +export type CursorPaginationParams = { + key?: string; + offset?: number; + limit: number; + count_total?: boolean; + reverse?: boolean; +}; + +export function buildCursorPaginationSearchParams( + pagination: CursorPaginationParams +): URLSearchParams { + const params = new URLSearchParams(); + params.set("pagination.limit", String(pagination.limit)); + + if (pagination.key) { + params.set("pagination.key", pagination.key); + } else if (pagination.offset !== undefined && pagination.offset > 0) { + params.set("pagination.offset", String(pagination.offset)); + } + + if (pagination.count_total !== undefined) { + params.set("pagination.count_total", String(pagination.count_total)); + } + if (pagination.reverse !== undefined) { + params.set("pagination.reverse", String(pagination.reverse)); + } + + return params; +} + +export function hasNextCursorPage(nextKey: string | null | undefined): boolean { + return Boolean(nextKey && nextKey.length > 0); +} diff --git a/apps/webapp/src/features/home/ui/hosts-home.tsx b/apps/webapp/src/features/home/ui/hosts-home.tsx index 32f918ba..8afe1704 100644 --- a/apps/webapp/src/features/home/ui/hosts-home.tsx +++ b/apps/webapp/src/features/home/ui/hosts-home.tsx @@ -1,45 +1,40 @@ "use client"; -import { Suspense, useMemo } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { Suspense, useEffect, useMemo } from "react"; +import { useRouter } from "next/navigation"; import { TableLayout, TableNullableCell } from "@shinzo/ui/table"; -import { - getServerPage, - Pagination, - DEFAULT_LIMIT, - type PageParams, -} from "@shinzo/ui/pagination"; +import { Pagination } from "@shinzo/ui/pagination"; import { useRegisteredHosts } from "../hooks/use-registered-hosts"; +import { useCursorPagePagination } from "../hooks/use-cursor-page-pagination"; const HOSTS_PAGE_PARAM = "hostsPage"; +const HOSTS_CURSOR_KEY = "registered-hosts-cursor-key"; +const PAGE_SIZE = 5; const tableHeadings = ["Address", "DID", "Connection String"]; function HostsHomeContent() { - const searchParams = useSearchParams(); const router = useRouter(); - - const { page, offset, limit }: PageParams = useMemo( - () => - getServerPage({ - page: searchParams.get(HOSTS_PAGE_PARAM) ?? "1", - limit: String(DEFAULT_LIMIT), - }), - [searchParams] - ); - - const queryParams = useMemo( - () => ({ - offset, - limit, - count_total: true, - }), - [offset, limit] - ); + const { page, queryParams, applyPaginationData, totalItems } = + useCursorPagePagination({ + pageParam: HOSTS_PAGE_PARAM, + storageKey: HOSTS_CURSOR_KEY, + limit: PAGE_SIZE, + }); const { data: registeredHosts, isPending } = useRegisteredHosts(queryParams); const hosts = registeredHosts?.hosts ?? []; - const total = Number(registeredHosts?.pagination?.total ?? 0); + const pageTotal = Number(registeredHosts?.pagination?.total ?? 0); + const nextKey = registeredHosts?.pagination?.next_key; + + useEffect(() => { + if (registeredHosts) { + applyPaginationData(nextKey, pageTotal); + } + }, [registeredHosts, nextKey, pageTotal, applyPaginationData]); + + const showPagination = useMemo(() => totalItems > PAGE_SIZE, [totalItems]); + const handleRegisterAsHost = () => { router.push("/host-registration"); }; @@ -66,7 +61,7 @@ function HostsHomeContent() {
0 ? tableHeadings : [""]} gridClass="grid-cols[repeat(3,1fr)]" @@ -98,12 +93,12 @@ function HostsHomeContent() { )} /> - {total > DEFAULT_LIMIT && ( + {showPagination && (
diff --git a/apps/webapp/src/features/home/ui/indexers-home.tsx b/apps/webapp/src/features/home/ui/indexers-home.tsx index 37e345b1..946fa77f 100644 --- a/apps/webapp/src/features/home/ui/indexers-home.tsx +++ b/apps/webapp/src/features/home/ui/indexers-home.tsx @@ -1,45 +1,40 @@ "use client"; -import { Suspense, useMemo } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { Suspense, useEffect, useMemo } from "react"; +import { useRouter } from "next/navigation"; import { TableLayout, TableNullableCell } from "@shinzo/ui/table"; -import { - getServerPage, - Pagination, - DEFAULT_LIMIT, - type PageParams, -} from "@shinzo/ui/pagination"; +import { Pagination } from "@shinzo/ui/pagination"; import { useRegisteredIndexers } from "../hooks/use-registered-indexers"; +import { useCursorPagePagination } from "../hooks/use-cursor-page-pagination"; const INDEXERS_PAGE_PARAM = "indexersPage"; +const INDEXERS_CURSOR_KEY = "registered-indexers-cursor-key"; +const PAGE_SIZE = 5; const tableHeadings = ["Address", "DID", "Chain", "Connection String"]; function IndexersHomeContent() { - const searchParams = useSearchParams(); const router = useRouter(); + const { page, queryParams, applyPaginationData, totalItems } = + useCursorPagePagination({ + pageParam: INDEXERS_PAGE_PARAM, + storageKey: INDEXERS_CURSOR_KEY, + limit: PAGE_SIZE, + }); - const { page, offset, limit }: PageParams = useMemo( - () => - getServerPage({ - page: searchParams.get(INDEXERS_PAGE_PARAM) ?? "1", - limit: String(DEFAULT_LIMIT), - }), - [searchParams] - ); - - const queryParams = useMemo( - () => ({ - offset, - limit, - count_total: true, - }), - [offset, limit] - ); const { data: registeredIndexers, isPending } = useRegisteredIndexers(queryParams); const indexers = registeredIndexers?.indexers ?? []; - const total = Number(registeredIndexers?.pagination?.total ?? 0); + const pageTotal = Number(registeredIndexers?.pagination?.total ?? 0); + const nextKey = registeredIndexers?.pagination?.next_key; + + useEffect(() => { + if (registeredIndexers) { + applyPaginationData(nextKey, pageTotal); + } + }, [registeredIndexers, nextKey, pageTotal, applyPaginationData]); + + const showPagination = useMemo(() => totalItems > PAGE_SIZE, [totalItems]); const handleRegisterAsIndexer = () => { router.push("/indexer-registration"); @@ -67,7 +62,7 @@ function IndexersHomeContent() {
0 ? tableHeadings : [""]} gridClass="grid-cols[repeat(4,1fr)]" @@ -107,12 +102,12 @@ function IndexersHomeContent() { )} /> - {total > DEFAULT_LIMIT && ( + {showPagination && (