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} +
+
+
{total}
resolved
+
{buyerPct}%
for buyer
+
{sellerPct}%
for seller
+
+
+ ) + })} + {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, + }) +}