Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f503380
use namespace id and avatar url hooks
notrab Sep 16, 2025
630a6f9
formatting
notrab Sep 17, 2025
5342311
Update apps/ensadmin/src/hooks/useAvatarUrl.ts
notrab Sep 17, 2025
4f135b6
Update apps/ensadmin/src/hooks/useNamespaceId.ts
notrab Sep 17, 2025
4228c30
Update apps/ensadmin/src/hooks/useNamespaceId.ts
notrab Sep 17, 2025
b5105a7
Update apps/ensadmin/src/hooks/useNamespaceId.ts
notrab Sep 17, 2025
4ef3b92
rename network to namespace
notrab Sep 17, 2025
eb13280
update jsdoc for avatar hook
notrab Sep 17, 2025
92e6a41
remove isLoading state
notrab Sep 17, 2025
25f8726
avatar url
notrab Sep 17, 2025
299a5b5
simplify hooks
notrab Sep 17, 2025
67abb36
fix ens app link
notrab Sep 17, 2025
114f7fd
Apply fixes (#1076)
lightwalker-eth Sep 18, 2025
ffe633a
Fix docs for useNamespaceId
lightwalker-eth Sep 18, 2025
ca4f9fc
Implement unexecuted suggestion
lightwalker-eth Sep 18, 2025
d59b382
Fix JSDoc for useAvatarUrl
lightwalker-eth Sep 18, 2025
94b8db9
improve clarity of ideas
lightwalker-eth Sep 18, 2025
2761e48
Improve JSDoc
lightwalker-eth Sep 18, 2025
79ccf65
refactor and fix namespace access and ens app url
shrugs Sep 18, 2025
26723f3
Merge branch 'main' into use-namespace-id
shrugs Sep 18, 2025
175abaf
fix: lint
shrugs Sep 18, 2025
37422df
Merge branch 'main' into use-namespace-id
shrugs Sep 19, 2025
8b3db1f
fix: only access namespace in ensadmin-specific components with useAc…
shrugs Sep 19, 2025
6a034c5
refactor: naming pattern, re-org hooks folder for clarity
shrugs Sep 19, 2025
59f0a0c
docstrings
shrugs Sep 19, 2025
3854ec7
docstrings
shrugs Sep 19, 2025
9e187d4
naming
shrugs Sep 19, 2025
ce29679
tweak jsdoc
notrab Sep 20, 2025
ea956a0
Merge branch 'main' into use-namespace-id
notrab Sep 20, 2025
bdfbcd4
docs(changeset): New hooks useNamespace, useEnsMetadataServiceAvatarU…
notrab Sep 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quick-pandas-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ensadmin": minor
---

New hooks useNamespace, useEnsMetadataServiceAvatarUrl, and useENSAppProfileUrl
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@
"typescript.preferences.importModuleSpecifier": "non-relative",
"[terraform]": {
"editor.defaultFormatter": "hashicorp.terraform"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
2 changes: 1 addition & 1 deletion apps/ensadmin/src/app/@actions/api/subgraph/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { CopyButton } from "@/components/ui/copy-button";
import { useActiveENSNodeUrl } from "@/hooks/active-ensnode-url";
import { useActiveENSNodeUrl } from "@/hooks/active/use-active-ensnode-url";

export default function ActionsSubgraphCompatPage() {
const baseUrl = useActiveENSNodeUrl();
Expand Down
17 changes: 7 additions & 10 deletions apps/ensadmin/src/app/@actions/name/[name]/page.tsx
Comment thread
notrab marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@

import { ExternalLinkWithIcon } from "@/components/external-link-with-icon";
import { Button } from "@/components/ui/button";
import { getExternalEnsAppNameUrl } from "@/lib/namespace-utils";
import { ENSNamespaceIds } from "@ensnode/datasources";
import { useENSAppProfileUrl } from "@/hooks/async/use-ens-app-profile-url";
import { useParams } from "next/navigation";

export default function ActionsNamePage() {
const params = useParams();
const name = decodeURIComponent(params.name as string);
const { name } = useParams<{ name: string }>();
Comment thread
shrugs marked this conversation as resolved.

// TODO: Get the namespace from the active ENSNode connection
// For now, defaulting to Mainnet
const namespaceId = ENSNamespaceIds.Mainnet;
const ensAppUrl = getExternalEnsAppNameUrl(name, namespaceId);
const { data: ensAppProfileUrl } = useENSAppProfileUrl(name);

if (!ensAppUrl) return null;
if (!ensAppProfileUrl) return null;

return (
<Button variant="link" size="sm" asChild>
<ExternalLinkWithIcon href={ensAppUrl.toString()}>View in ENS App</ExternalLinkWithIcon>
<ExternalLinkWithIcon href={ensAppProfileUrl.toString()}>
View in ENS App
</ExternalLinkWithIcon>
</Button>
);
}
2 changes: 1 addition & 1 deletion apps/ensadmin/src/app/api/subgraph/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { SubgraphGraphiQLEditor } from "@/components/graphiql-editor";
import { useActiveENSNodeUrl } from "@/hooks/active-ensnode-url";
import { useActiveENSNodeUrl } from "@/hooks/active/use-active-ensnode-url";

export default function SubgraphGraphQLPage() {
const baseUrl = useActiveENSNodeUrl();
Expand Down
19 changes: 11 additions & 8 deletions apps/ensadmin/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { Suspense } from "react";
import "./globals.css";

import { AppSidebar } from "@/components/app-sidebar";
import { ENSNodeProvider } from "@/components/providers/ensnode-provider";
import { ActiveENSNodeProvider } from "@/components/providers/active-ensnode-provider";
import { QueryClientProvider } from "@/components/query-client/components";
import { RequireActiveENSNodeConfig } from "@/components/require-active-ensnode-config";
import { RequireActiveENSNodeConnection } from "@/components/require-active-ensnode-connection";
import { Header, HeaderActions, HeaderBreadcrumbs, HeaderNav } from "@/components/ui/header";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
Expand Down Expand Up @@ -71,13 +72,15 @@ export default function Layout({
<AppSidebar />
</Suspense>
<SidebarInset className="min-w-0">
<Header>
<HeaderNav>
<HeaderBreadcrumbs>{breadcrumbs}</HeaderBreadcrumbs>
</HeaderNav>
<HeaderActions>{actions}</HeaderActions>
</Header>
<ENSNodeProvider>{children}</ENSNodeProvider>
<ActiveENSNodeProvider>
<Header>
<HeaderNav>
<HeaderBreadcrumbs>{breadcrumbs}</HeaderBreadcrumbs>
</HeaderNav>
<HeaderActions>{actions}</HeaderActions>
</Header>
<RequireActiveENSNodeConfig>{children}</RequireActiveENSNodeConfig>
</ActiveENSNodeProvider>
</SidebarInset>
</SidebarProvider>
</RequireActiveENSNodeConnection>
Expand Down
53 changes: 53 additions & 0 deletions apps/ensadmin/src/app/name/[name]/NameDetailPageSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use client";

import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";

export function NameDetailPageSkeleton() {
return (
<div className="container mx-auto p-6 max-w-4xl">
<div className="mb-8">
<div className="flex items-center gap-4 mb-4">
<Skeleton className="h-20 w-20 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-8 w-48" />
<div className="flex items-center gap-2">
<Skeleton className="h-6 w-20" />
<Skeleton className="h-8 w-32" />
</div>
</div>
</div>
</div>

<div className="space-y-6">
<Card>
<CardHeader>
<Skeleton className="h-6 w-32" />
</CardHeader>
<CardContent className="space-y-4">
<div>
<Skeleton className="h-4 w-20 mb-1" />
<Skeleton className="h-4 w-full" />
</div>
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-4 w-32" />
</div>
</CardContent>
</Card>

<Card>
<CardHeader>
<Skeleton className="h-6 w-24" />
</CardHeader>
<CardContent className="space-y-3">
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-4 w-24" />
</div>
</CardContent>
</Card>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import { ExternalLinkWithIcon } from "@/components/external-link-with-icon";
import { NameDisplay } from "@/components/identity/utils";
import { Avatar } from "@/components/ui/avatar";
import { Card, CardContent } from "@/components/ui/card";
import { useActiveNamespace } from "@/hooks/active/use-active-namespace";
import { beautifyUrl } from "@/lib/beautify-url";
import { ENSNamespaceId } from "@ensnode/datasources";
import { Name } from "@ensnode/ensnode-sdk";

interface ProfileHeaderProps {
name: Name;
namespaceId: ENSNamespaceId;
headerImage?: string | null;
websiteUrl?: string | null;
}

export function ProfileHeader({ name, namespaceId, headerImage, websiteUrl }: ProfileHeaderProps) {
export function ProfileHeader({ name, headerImage, websiteUrl }: ProfileHeaderProps) {
const namespace = useActiveNamespace();

// Parse header image URI and only use it if it's HTTP/HTTPS
// TODO: Add support for more URI types as defined in ENSIP-12
// See: https://docs.ens.domains/ensip/12#uri-types
Expand Down Expand Up @@ -70,7 +71,7 @@ export function ProfileHeader({ name, namespaceId, headerImage, websiteUrl }: Pr
<Avatar
className="-mt-16 h-20 w-20 ring-4 ring-white"
ensName={name}
namespaceId={namespaceId}
namespaceId={namespace}
/>
<div className="flex-1">
<h1>
Expand Down
105 changes: 45 additions & 60 deletions apps/ensadmin/src/app/name/[name]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,48 @@
"use client";

import { Card, CardContent } from "@/components/ui/card";
import { ENSNamespaceIds } from "@ensnode/datasources";
import { useRecords } from "@ensnode/ensnode-react";
import { ResolverRecordsSelection, getCommonCoinTypes } from "@ensnode/ensnode-sdk";
import { useParams } from "next/navigation";

import { useActiveNamespace } from "@/hooks/active/use-active-namespace";
import { AdditionalRecords } from "./_components/AdditionalRecords";
import { Addresses } from "./_components/Addresses";
import { NameDetailPageSkeleton } from "./_components/NameDetailPageSkeleton";
import { ProfileHeader } from "./_components/ProfileHeader";
import { ProfileInformation } from "./_components/ProfileInformation";
import { SocialLinks } from "./_components/SocialLinks";

export default function NameDetailPage() {
const params = useParams();
const name = decodeURIComponent(params.name as string);

// TODO: Get the namespace from the active ENSNode connection
const namespaceId = ENSNamespaceIds.Mainnet;
const HeaderPanelTextRecords = ["url", "avatar", "header"];
const ProfilePanelTextRecords = ["description", "email"];
const SocialLinksTextRecords = [
"com.twitter",
"com.github",
"com.farcaster",
"org.telegram",
"com.linkedin",
"com.reddit",
];
// TODO: Instead of explicitly listing AdditionalTextRecords, we should update
// `useRecords` so that we can ask it to return not only all the records we
// explicitly requested, but also any other records that were found onchain,
// no matter what their text record keys are. Below are two examples of
// additional text records set for lightwalker.eth on mainnet as an example.
// see: https://github.com/namehash/ensnode/issues/1083
const AdditionalTextRecords = ["status", "eth.ens.delegate"];
const AllRequestedTextRecords = [
...HeaderPanelTextRecords,
...ProfilePanelTextRecords,
...SocialLinksTextRecords,
...AdditionalTextRecords,
];

const HeaderPanelTextRecords = ["url", "avatar", "header"];
const ProfilePanelTextRecords = ["description", "email"];
const SocialLinksTextRecords = [
"com.twitter",
"com.github",
"com.farcaster",
"org.telegram",
"com.linkedin",
"com.reddit",
];
// TODO: Instead of explicitly listing AdditionalTextRecords, we should update
// `useRecords` so that we can ask it to return not only all the records we
// explicitly requested, but also any other records that were found onchain,
// no matter what their text record keys are. Below are two examples of
// additional text records set for lightwalker.eth on mainnet as an example.
// see: https://github.com/namehash/ensnode/issues/1083
const AdditionalTextRecords = ["status", "eth.ens.delegate"];
const AllRequestedTextRecords = [
...HeaderPanelTextRecords,
...ProfilePanelTextRecords,
...SocialLinksTextRecords,
...AdditionalTextRecords,
];
export default function NameDetailPage() {
const { name } = useParams<{ name: string }>();
Comment thread
notrab marked this conversation as resolved.
const namespace = useActiveNamespace();

const selection = {
addresses: getCommonCoinTypes(namespaceId),
addresses: getCommonCoinTypes(namespace),
texts: AllRequestedTextRecords,
} as const satisfies ResolverRecordsSelection;

Expand All @@ -66,46 +64,33 @@ export default function NameDetailPage() {

if (status === "pending") return <NameDetailPageSkeleton />;

if (status === "error")
return (
<Card>
<CardContent className="pt-6">
<p className="text-red-600">Failed to load profile information</p>
</CardContent>
</Card>
);

return (
<div className="container mx-auto p-6 max-w-4xl">
<ProfileHeader
name={name}
namespaceId={namespaceId}
headerImage={data?.records?.texts?.header}
websiteUrl={data?.records?.texts?.url}
/>

<div className="grid gap-6">
{(() => {
switch (status) {
case "error": {
return (
<Card>
<CardContent className="pt-6">
<p className="text-red-600">Failed to load profile information</p>
</CardContent>
</Card>
);
}

case "success": {
return (
<>
<ProfileInformation
description={data.records.texts.description}
email={data.records.texts.email}
/>
<ProfileInformation
description={data.records.texts.description}
email={data.records.texts.email}
/>

<SocialLinks.Texts texts={data.records.texts} />
<SocialLinks.Texts texts={data.records.texts} />

<Addresses addresses={data.records.addresses} />
<Addresses addresses={data.records.addresses} />

<AdditionalRecords texts={data.records.texts} />
</>
);
}
}
})()}
<AdditionalRecords texts={data.records.texts} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import { useActiveENSNodeUrl } from "@/hooks/active-ensnode-url";
import { useActiveENSNodeUrl } from "@/hooks/active/use-active-ensnode-url";
import { useENSNodeConnections } from "@/hooks/ensnode-connections";
import { useMutation } from "@tanstack/react-query";
import { CopyButton } from "../ui/copy-button";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function IndexingStatus() {
/>
);
}

if (indexingStatusQuery.isError) {
return <p className="p-6">Failed to fetch Indexing Status.</p>;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client";

import { useActiveENSNodeUrl } from "@/hooks/active/use-active-ensnode-url";
import { ENSNodeProvider as _ENSNodeProvider } from "@ensnode/ensnode-react";
import { PropsWithChildren } from "react";

/**
* Provider component that configures ENSNodeProvider with the currently active ENSNode connection URL.
*
* This component wraps the ENSNodeProvider from @ensnode/ensnode-react and automatically
* configures it with the URL from the currently active ENSNode connection. It serves as
* a bridge between the connection management system and the ENSNode React hooks.
*
* @param children - React children to render within the provider context
*/
export function ActiveENSNodeProvider({ children }: PropsWithChildren) {
const url = useActiveENSNodeUrl();

return <_ENSNodeProvider config={{ client: { url } }}>{children}</_ENSNodeProvider>;
}
10 changes: 0 additions & 10 deletions apps/ensadmin/src/components/providers/ensnode-provider.tsx

This file was deleted.

Loading