diff --git a/frontend/src/components/AdminInvoiceListItem.tsx b/frontend/src/components/AdminInvoiceListItem.tsx index b747300..354b2a2 100644 --- a/frontend/src/components/AdminInvoiceListItem.tsx +++ b/frontend/src/components/AdminInvoiceListItem.tsx @@ -2,9 +2,9 @@ import { A } from "@solidjs/router" import { Show } from "solid-js" import { getProfilePicture } from "applesauce-core/helpers/profile" import { invoiceStatus, type Invoice } from "@/lib/api" +import CopyNpub from "@/components/CopyNpub" import { useProfileMetadata } from "@/lib/hooks" import { formatPeriod, formatUsd } from "@/lib/format" -import { shortenPubkey } from "@/lib/pubkey" const invoiceStatusStyles: Record = { open: "bg-yellow-50 text-yellow-700 border-yellow-200", @@ -45,7 +45,12 @@ export default function AdminInvoiceListItem(props: AdminInvoiceListItemProps) { > - {(metadata()?.name || metadata()?.display_name) || shortenPubkey(props.invoice.tenant_pubkey)} + } + > + {metadata()?.name || metadata()?.display_name} + diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx index 29f0cea..ea9daaf 100644 --- a/frontend/src/components/AppShell.tsx +++ b/frontend/src/components/AppShell.tsx @@ -7,11 +7,8 @@ import { RELAY_DOMAIN } from "@/lib/subdomain" import serverIcon from "@/assets/server.svg" import Modal from "@/components/Modal" import BillingPrompts from "@/components/BillingPrompts" - -function shortenPubkey(pubkey?: string) { - if (!pubkey) return "" - return `${pubkey.slice(0, 8)}…${pubkey.slice(-6)}` -} +import CopyNpub from "@/components/CopyNpub" +import { shortenNpub } from "@/lib/pubkey" function SearchIcon() { return ( @@ -43,7 +40,12 @@ export default function AppShell(props: { children?: any }) { const [searchQuery, setSearchQuery] = createSignal("") const [adminOpen, setAdminOpen] = createSignal(false) - const username = createMemo(() => metadata()?.name || metadata()?.display_name || shortenPubkey(account()?.pubkey)) + const displayName = createMemo(() => metadata()?.name || metadata()?.display_name) + const username = createMemo(() => { + const pubkey = account()?.pubkey + return displayName() || (pubkey ? shortenNpub(pubkey) : "") + }) + const initial = createMemo(() => (displayName() || account()?.pubkey || "?").slice(0, 1).toUpperCase()) const nip05 = createMemo(() => metadata()?.nip05) const searchedRelays = createMemo(() => { const list = tenantRelays() ?? [] @@ -110,14 +112,19 @@ export default function AppShell(props: { children?: any }) { when={picture()} fallback={
- {username().slice(0, 1).toUpperCase()} + {initial()}
} > Profile
-

{username()}

+ } + > +

{username()}

+

{nip05()}

@@ -169,7 +176,7 @@ export default function AppShell(props: { children?: any }) { {username().slice(0, 1).toUpperCase()}} + fallback={
{initial()}
} > Profile
diff --git a/frontend/src/components/CopyNpub.tsx b/frontend/src/components/CopyNpub.tsx new file mode 100644 index 0000000..cee429b --- /dev/null +++ b/frontend/src/components/CopyNpub.tsx @@ -0,0 +1,33 @@ +import { copyToClipboard } from "@/lib/clipboard" +import { shortenNpub, toNpub } from "@/lib/pubkey" + +// Renders a pubkey as a shortened npub followed by a small copy button. The button +// copies the full npub and toasts on success. The icon inherits the surrounding +// text color (currentColor + opacity) so it reads on both light and dark +// backgrounds, and the click stops propagation so it can sit inside links/cards +// without triggering them. Pass `class` to style the npub text. +export default function CopyNpub(props: { pubkey: string; class?: string }) { + const copy = (event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + void copyToClipboard(toNpub(props.pubkey), { successMessage: "npub copied to clipboard" }) + } + + return ( + + {shortenNpub(props.pubkey)} + + + ) +} diff --git a/frontend/src/components/InvoiceDetailCard.tsx b/frontend/src/components/InvoiceDetailCard.tsx index dbbfb16..e9c534c 100644 --- a/frontend/src/components/InvoiceDetailCard.tsx +++ b/frontend/src/components/InvoiceDetailCard.tsx @@ -3,9 +3,9 @@ import { Show } from "solid-js" import { getProfilePicture } from "applesauce-core/helpers/profile" import { invoiceStatus, type Invoice } from "@/lib/api" import Field from "@/components/Field" +import CopyNpub from "@/components/CopyNpub" import { useProfileMetadata } from "@/lib/hooks" import { formatPeriod, formatUsd } from "@/lib/format" -import { shortenPubkey } from "@/lib/pubkey" const invoiceStatusStyles: Record = { open: "bg-yellow-50 text-yellow-700 border-yellow-200", @@ -59,7 +59,12 @@ export default function InvoiceDetailCard(props: InvoiceDetailCardProps) { > - {(metadata()?.name || metadata()?.display_name) || shortenPubkey(props.invoice.tenant_pubkey)} + } + > + {metadata()?.name || metadata()?.display_name} +
diff --git a/frontend/src/components/RelayListItem.tsx b/frontend/src/components/RelayListItem.tsx index df03a16..f498361 100644 --- a/frontend/src/components/RelayListItem.tsx +++ b/frontend/src/components/RelayListItem.tsx @@ -3,8 +3,8 @@ import { Show } from "solid-js" import { getProfilePicture } from "applesauce-core/helpers/profile" import type { Relay } from "@/lib/api" import { PlanBadge, StatusBadge } from "@/components/relay/RelayCardHeader" +import CopyNpub from "@/components/CopyNpub" import { useProfileMetadata } from "@/lib/hooks" -import { shortenPubkey } from "@/lib/pubkey" import { RELAY_DOMAIN } from "@/lib/subdomain" type RelayListItemProps = { @@ -38,7 +38,12 @@ export default function RelayListItem(props: RelayListItemProps) { > - {(metadata()?.name || metadata()?.display_name) || shortenPubkey(props.relay.tenant_pubkey)} + } + > + {metadata()?.name || metadata()?.display_name} + diff --git a/frontend/src/components/relay/RelayCardHeader.tsx b/frontend/src/components/relay/RelayCardHeader.tsx index 5fa7aa2..79b36d9 100644 --- a/frontend/src/components/relay/RelayCardHeader.tsx +++ b/frontend/src/components/relay/RelayCardHeader.tsx @@ -3,7 +3,7 @@ import { Show, createEffect, createSignal, onCleanup } from "solid-js" import { getProfilePicture, type ProfileContent } from "applesauce-core/helpers/profile" import type { Relay } from "@/lib/api" import menuDotsIcon from "@/assets/menu-dots-2.svg" -import { shortenPubkey } from "@/lib/pubkey" +import CopyNpub from "@/components/CopyNpub" import { RELAY_DOMAIN } from "@/lib/subdomain" const STATUS_STYLES: Record = { @@ -113,7 +113,12 @@ export default function RelayCardHeader(props: RelayCardHeaderProps) { > - {(metadata()?.name || metadata()?.display_name) || shortenPubkey(r().tenant_pubkey)} + } + > + {metadata()?.name || metadata()?.display_name} + diff --git a/frontend/src/lib/pubkey.ts b/frontend/src/lib/pubkey.ts index fdd5848..3715efa 100644 --- a/frontend/src/lib/pubkey.ts +++ b/frontend/src/lib/pubkey.ts @@ -1,3 +1,19 @@ +import { npubEncode } from "applesauce-core/helpers/pointers" + export function shortenPubkey(pubkey: string) { return pubkey.length <= 16 ? pubkey : `${pubkey.slice(0, 8)}...${pubkey.slice(-8)}` } + +// Encode a hex pubkey as an npub for display. Falls back to the raw input when it +// isn't a valid pubkey, since npubEncode throws on malformed input. +export function toNpub(pubkey: string) { + try { + return npubEncode(pubkey) + } catch { + return pubkey + } +} + +export function shortenNpub(pubkey: string) { + return shortenPubkey(toNpub(pubkey)) +} diff --git a/frontend/src/pages/admin/AdminTenantDetail.tsx b/frontend/src/pages/admin/AdminTenantDetail.tsx index 4d2b7ce..b7737a4 100644 --- a/frontend/src/pages/admin/AdminTenantDetail.tsx +++ b/frontend/src/pages/admin/AdminTenantDetail.tsx @@ -8,7 +8,8 @@ import AdminInvoiceListItem from "@/components/AdminInvoiceListItem" import ResourceState from "@/components/ResourceState" import useMinLoading from "@/lib/useMinLoading" import { useAdminTenant, useAdminTenantInvoices, useAdminTenantRelays, useProfileMetadata } from "@/lib/hooks" -import { shortenPubkey } from "@/lib/pubkey" +import CopyNpub from "@/components/CopyNpub" +import { shortenNpub } from "@/lib/pubkey" export default function AdminTenantDetail() { const params = useParams() @@ -40,8 +41,10 @@ export default function AdminTenantDetail() { Profile
-

{(metadata()?.name || metadata()?.display_name) || shortenPubkey(tenantId())}

-

{tenantId()}

+

{(metadata()?.name || metadata()?.display_name) || shortenNpub(tenantId())}

+

+ +

diff --git a/frontend/src/pages/admin/AdminTenantList.tsx b/frontend/src/pages/admin/AdminTenantList.tsx index cf2567c..4219587 100644 --- a/frontend/src/pages/admin/AdminTenantList.tsx +++ b/frontend/src/pages/admin/AdminTenantList.tsx @@ -6,7 +6,8 @@ import ResourceState from "@/components/ResourceState" import SearchInput from "@/components/SearchInput" import useMinLoading from "@/lib/useMinLoading" import { useAdminTenants, useProfileMetadataMap } from "@/lib/hooks" -import { shortenPubkey } from "@/lib/pubkey" +import CopyNpub from "@/components/CopyNpub" +import { shortenNpub } from "@/lib/pubkey" import { fuzzySearch } from "@/lib/search" export default function AdminTenantList() { @@ -56,9 +57,11 @@ export default function AdminTenantList() { Profile
-

{profileName() || shortenPubkey(tenant.pubkey)}

+

{profileName() || shortenNpub(tenant.pubkey)}

+

+ +

{profile()?.about || "No profile bio"}

-

{tenant.pubkey}