From 530ea63d3692d0e3b24f0e13bf0dcefe190f820c Mon Sep 17 00:00:00 2001
From: thesn10 <38666407+thesn10@users.noreply.github.com>
Date: Thu, 25 Jun 2026 12:38:41 +0200
Subject: [PATCH] feat: resolvers views and search fallback page
---
app/src/app/resolver/[pk]/page.tsx | 8 ++++
app/src/app/resolvers/page.tsx | 2 +
app/src/app/search/page.tsx | 20 ++++++++
app/src/components/ResolverDetail.tsx | 67 +++++++++++++++++++++++++++
app/src/components/ResolverGrid.tsx | 60 ++++++++++++++++++++++++
app/src/hooks/useResolvers.ts | 17 +++++++
6 files changed, 174 insertions(+)
create mode 100644 app/src/app/resolver/[pk]/page.tsx
create mode 100644 app/src/app/resolvers/page.tsx
create mode 100644 app/src/app/search/page.tsx
create mode 100644 app/src/components/ResolverDetail.tsx
create mode 100644 app/src/components/ResolverGrid.tsx
create mode 100644 app/src/hooks/useResolvers.ts
diff --git a/app/src/app/resolver/[pk]/page.tsx b/app/src/app/resolver/[pk]/page.tsx
new file mode 100644
index 0000000..a3c9a84
--- /dev/null
+++ b/app/src/app/resolver/[pk]/page.tsx
@@ -0,0 +1,8 @@
+'use client'
+import { use } from 'react'
+import { ResolverDetail } from '@/components/ResolverDetail'
+import type { Address } from '@solisting/sdk'
+export default function ResolverPage({ params }: { params: Promise<{ pk: string }> }) {
+ const { pk } = use(params)
+ return
+}
diff --git a/app/src/app/resolvers/page.tsx b/app/src/app/resolvers/page.tsx
new file mode 100644
index 0000000..98edb74
--- /dev/null
+++ b/app/src/app/resolvers/page.tsx
@@ -0,0 +1,2 @@
+import { ResolverGrid } from '@/components/ResolverGrid'
+export default function ResolversPage() { return }
diff --git a/app/src/app/search/page.tsx b/app/src/app/search/page.tsx
new file mode 100644
index 0000000..0656c62
--- /dev/null
+++ b/app/src/app/search/page.tsx
@@ -0,0 +1,20 @@
+'use client'
+import { useSearchParams, useRouter } from 'next/navigation'
+import { Suspense } from 'react'
+function SearchContent() {
+ const params = useSearchParams()
+ const router = useRouter()
+ const q = params.get('q') ?? ''
+ return (
+
+
GLOBAL SEARCH
+
No account found
+
Nothing on-chain matched this query on the selected network.
+
{q}
+
+
+ )
+}
+export default function SearchPage() {
+ return
+}
diff --git a/app/src/components/ResolverDetail.tsx b/app/src/components/ResolverDetail.tsx
new file mode 100644
index 0000000..396e393
--- /dev/null
+++ b/app/src/components/ResolverDetail.tsx
@@ -0,0 +1,67 @@
+'use client'
+
+import { useRouter } from 'next/navigation'
+import { ResolverType, AcceptancePolicy } from '@descro/sdk'
+import { useResolvers } from '@/hooks/useResolvers'
+import { FieldRow, MonoChip } from '@/components/ui/FieldRow'
+import { abbrev } from '@/lib/format'
+import type { Address } from '@solisting/sdk'
+
+export function ResolverDetail({ pk }: { pk: Address }) {
+ const router = useRouter()
+ const { data: resolvers = [], isLoading } = useResolvers()
+ const r = resolvers.find((x) => x.pda === pk)
+
+ if (isLoading) return Loading…
+ if (!r) return Resolver entry not found.
+
+ const d = r.account
+ const total = Number(d.ruledForBuyer) + Number(d.ruledForSeller)
+ const buyerPct = total > 0 ? Math.round(Number(d.ruledForBuyer) / total * 100) : 0
+ const sellerPct = total > 0 ? 100 - buyerPct : 0
+
+ return (
+
+
+
RESOLVER ENTRY · {abbrev(pk)}
+
{d.name || abbrev(pk)}
+ {d.description &&
{d.description}
}
+
+
+
+
ACCOUNT FIELDS
+
navigator.clipboard.writeText(d.authority)} />
+
{ResolverType[d.resolverType as number] as string}
+
{AcceptancePolicy[d.acceptancePolicy as number] as string}
+
{(d.feeBps / 100).toFixed(2)}%
+
navigator.clipboard.writeText(d.feeRecipient)} />
+ {d.metadataUri &&
{d.metadataUri}}
+
+
+
+
DISPUTE STATISTICS
+
+
{total}
+
total resolved
+
+ {[
+ { label: 'Ruled for buyer', count: Number(d.ruledForBuyer), pct: buyerPct, color: 'var(--acc2)', barColor: 'var(--acc2)' },
+ { label: 'Ruled for seller', count: Number(d.ruledForSeller), pct: sellerPct, color: '#F5B23E', barColor: '#F5B23E' },
+ ].map((stat) => (
+
+
+ {stat.label}
+ {stat.count} · {stat.pct}%
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/app/src/components/ResolverGrid.tsx b/app/src/components/ResolverGrid.tsx
new file mode 100644
index 0000000..140a122
--- /dev/null
+++ b/app/src/components/ResolverGrid.tsx
@@ -0,0 +1,60 @@
+'use client'
+
+import { useRouter } from 'next/navigation'
+import { ResolverType, AcceptancePolicy } from '@descro/sdk'
+import { useResolvers } from '@/hooks/useResolvers'
+import { abbrev } from '@/lib/format'
+
+export function ResolverGrid() {
+ const router = useRouter()
+ const { data: resolvers = [], isLoading } = useResolvers()
+
+ if (isLoading) return Loading…
+
+ return (
+
+
+
Resolver Registry
+
+ {resolvers.length} ResolverEntry PDAs · descro_ext_resolvers program
+
+
+
+ {resolvers.map((r) => {
+ const d = r.account
+ const total = Number(d.ruledForBuyer) + Number(d.ruledForSeller)
+ const buyerPct = total > 0 ? Math.round(Number(d.ruledForBuyer) / total * 100) : 0
+ const sellerPct = total > 0 ? 100 - buyerPct : 0
+ const feePct = (d.feeBps / 100).toFixed(2)
+ const resolverTypeName = ResolverType[d.resolverType as number] as string
+ const policyName = AcceptancePolicy[d.acceptancePolicy as number] as string
+ return (
+
router.push(`/resolver/${r.pda}`)}
+ style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: 'var(--radius)', padding: '20px 22px', cursor: 'pointer' }}
+ >
+
+
+
{d.name || abbrev(r.pda)}
+
{abbrev(r.pda)}
+
+
{feePct}% fee
+
+
+ {resolverTypeName}
+ {policyName}
+
+
+
+ )
+ })}
+ {resolvers.length === 0 &&
No resolvers registered.
}
+
+
+ )
+}
diff --git a/app/src/hooks/useResolvers.ts b/app/src/hooks/useResolvers.ts
new file mode 100644
index 0000000..9610f3c
--- /dev/null
+++ b/app/src/hooks/useResolvers.ts
@@ -0,0 +1,17 @@
+import { useQuery } from '@tanstack/react-query'
+import { useCluster } from '@solana/connector/react'
+import { createSolanaRpc } from '@solana/kit'
+import { fetchAllResolvers } from '@descro/sdk'
+
+export function useResolvers() {
+ const { cluster } = useCluster()
+ return useQuery({
+ queryKey: ['resolvers', cluster?.id],
+ queryFn: () => {
+ const rpc = createSolanaRpc(cluster!.url)
+ return fetchAllResolvers(rpc as Parameters[0])
+ },
+ enabled: !!cluster,
+ refetchInterval: 30_000,
+ })
+}