more cleanup

This commit is contained in:
hzrd149 2024-11-19 08:44:43 -06:00
parent 7777b3382e
commit dbd724a118
27 changed files with 80 additions and 122 deletions

View File

@ -19,14 +19,14 @@ import {
Text,
} from "@chakra-ui/react";
import { NostrEvent, kinds, nip19 } from "nostr-tools";
import { encodeDecodeResult } from "applesauce-core/helpers";
import { encodeDecodeResult, getProfileContent, ProfileContent } from "applesauce-core/helpers";
import { ExternalLinkIcon } from "../icons";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import useSingleEvent from "../../hooks/use-single-event";
import useReplaceableEvent from "../../hooks/use-replaceable-event";
import { useReadRelays } from "../../hooks/use-client-relays";
import { Kind0ParsedContent, getDisplayName, parseMetadataContent } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import { MetadataAvatar } from "../user/user-avatar";
import HoverLinkOverlay from "../hover-link-overlay";
import ArrowRight from "../icons/arrow-right";
@ -62,7 +62,7 @@ function getKindFromDecoded(decoded: nip19.DecodeResult) {
}
function AppHandler({ app, decoded }: { app: NostrEvent; decoded: nip19.DecodeResult }) {
const metadata = useMemo(() => parseMetadataContent(app), [app]);
const metadata = useMemo(() => getProfileContent(app), [app]);
const link = useMemo(() => {
const tag = app.tags.find((t) => t[0] === "web" && t[2] === decoded.type) || app.tags.find((t) => t[0] === "web");
return tag ? tag[1].replace("<bech32>", encodeDecodeResult(decoded)) : undefined;
@ -111,7 +111,7 @@ export default function AppHandlerModal({
const filteredApps = apps.filter((app) => {
if (search.length > 1) {
try {
const parsed = JSON.parse(app.content) as Kind0ParsedContent;
const parsed = JSON.parse(app.content) as ProfileContent;
if (getDisplayName(parsed, app.pubkey).toLowerCase().includes(search.toLowerCase())) {
return true;
}

View File

@ -4,7 +4,7 @@ import { MenuItem, useToast } from "@chakra-ui/react";
import { NostrEvent } from "../../types/nostr-event";
import { ShareIcon } from "../icons";
import useUserProfile from "../../hooks/use-user-profile";
import { getDisplayName } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import useShareableEventAddress from "../../hooks/use-shareable-event-address";
export default function ShareLinkMenuItem({ event }: { event: NostrEvent }) {

View File

@ -16,7 +16,7 @@ import { Link as RouterLink } from "react-router-dom";
import { nip19 } from "nostr-tools";
import UserAvatar from "./user/user-avatar";
import { getDisplayName } from "../helpers/nostr/user-metadata";
import { getDisplayName } from "../helpers/nostr/profile";
import useUserProfile from "../hooks/use-user-profile";
function UserTag({ pubkey, ...props }: { pubkey: string } & Omit<TagProps, "children">) {

View File

@ -11,10 +11,9 @@ import {
import dayjs from "dayjs";
import { kinds } from "nostr-tools";
import { getValue } from "applesauce-core/observable";
import { getInboxes, getOutboxes } from "applesauce-core/helpers";
import { getInboxes, getOutboxes, safeRelayUrls } from "applesauce-core/helpers";
import { DraftNostrEvent, NostrEvent, isDTag } from "../../types/nostr-event";
import clientRelaysService from "../../services/client-relays";
import { getZapSplits } from "../../helpers/nostr/zaps";
import { unique } from "../../helpers/array";
import relayScoreboardService from "../../services/relay-scoreboard";
@ -81,7 +80,7 @@ async function getPayRequestForPubkey(
content: comment ?? "",
tags: [
["p", pubkey],
["relays", ...unique([...userInbox, ...eventRelays, ...outbox, ...additional])],
["relays", ...unique(safeRelayUrls([...userInbox, ...eventRelays, ...outbox, ...additional]))],
["amount", String(amount)],
],
};

View File

@ -2,7 +2,7 @@ import { CloseIcon } from "@chakra-ui/icons";
import { useNavigate, Link as RouterLink } from "react-router-dom";
import { Box, Button, ButtonGroup, Flex, IconButton, Text, useDisclosure } from "@chakra-ui/react";
import { getDisplayName } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import useUserProfile from "../../hooks/use-user-profile";
import accountService from "../../services/account";
import { AddIcon, ChevronDownIcon, ChevronUpIcon } from "../icons";

View File

@ -8,7 +8,7 @@ import { nip19 } from "nostr-tools";
import UserAvatar from "../user/user-avatar";
import useUserProfile from "../../hooks/use-user-profile";
import { getDisplayName } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import { userSearchDirectory } from "../../services/username-search";
function UserOption({ pubkey }: { pubkey: string }) {

View File

@ -2,10 +2,11 @@ import { forwardRef, memo, useMemo } from "react";
import { Avatar, AvatarProps } from "@chakra-ui/react";
import { useAsync } from "react-use";
import styled from "@emotion/styled";
import { ProfileContent } from "applesauce-core/helpers";
import { getIdenticon } from "../../helpers/identicon";
import { safeUrl } from "../../helpers/parse";
import { Kind0ParsedContent, getDisplayName } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import useAppSettings from "../../hooks/use-app-settings";
import useCurrentAccount from "../../hooks/use-current-account";
import { buildImageProxyURL } from "../../helpers/image";
@ -79,7 +80,7 @@ const SquareAvatarWithBorder = styled(SquareAvatar)`
`;
export type MetadataAvatarProps = Omit<AvatarProps, "src"> & {
metadata?: Kind0ParsedContent;
metadata?: ProfileContent;
pubkey?: string;
noProxy?: boolean;
square?: boolean;

View File

@ -2,7 +2,7 @@ import { Link, LinkProps } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { nip19 } from "nostr-tools";
import { getDisplayName } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import useUserProfile from "../../hooks/use-user-profile";
import useAppSettings from "../../hooks/use-app-settings";
import useCurrentAccount from "../../hooks/use-current-account";

View File

@ -1,7 +1,7 @@
import { memo } from "react";
import { Text, TextProps } from "@chakra-ui/react";
import { getDisplayName } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import useUserProfile from "../../hooks/use-user-profile";
import useAppSettings from "../../hooks/use-app-settings";

View File

@ -1,5 +1,5 @@
import { safeRelayUrls } from "applesauce-core/helpers";
import { kinds } from "nostr-tools";
import { safeRelayUrl, safeRelayUrls } from "./helpers/relay";
export const DEFAULT_SEARCH_RELAYS = safeRelayUrls([
"wss://relay.nostr.band",
@ -8,11 +8,10 @@ export const DEFAULT_SEARCH_RELAYS = safeRelayUrls([
"wss://filter.nostr.wine",
]);
export const WIKI_RELAYS = safeRelayUrls(["wss://relay.wikifreedia.xyz/"]);
export const COMMON_CONTACT_RELAY = safeRelayUrl("wss://purplepag.es") as string;
export const COMMON_CONTACT_RELAYS = [COMMON_CONTACT_RELAY];
export const COMMON_CONTACT_RELAYS = safeRelayUrls(["wss://purplepag.es/"]);
export const DEFAULT_SIGNAL_RELAYS = safeRelayUrls(["wss://nostrue.com/", "wss://relay.damus.io"]);
export const DEFAULT_NOSTR_CONNECT_RELAYS = safeRelayUrls(["wss://relay.nsec.app"]);
export const DEFAULT_NOSTR_CONNECT_RELAYS = safeRelayUrls(["wss://relay.nsec.app/"]);
export const DEFAULT_ICE_SERVERS: RTCIceServer[] = [
{

View File

@ -0,0 +1,22 @@
import { nip19 } from "nostr-tools";
import emojiRegex from "emoji-regex";
import { truncatedId } from "./event";
import { ProfileContent } from "applesauce-core/helpers";
export function getSearchNames(profile: ProfileContent) {
if (!profile) return [];
return [profile.displayName, profile.display_name, profile.name].filter(Boolean) as string[];
}
const matchEmoji = emojiRegex();
export function getDisplayName(metadata: ProfileContent | undefined, pubkey: string, removeEmojis = false) {
let displayName = metadata?.displayName || metadata?.display_name || metadata?.name;
if (displayName) {
if (removeEmojis) displayName = displayName.replaceAll(matchEmoji, "");
return displayName;
}
return truncatedId(nip19.npubEncode(pubkey));
}

View File

@ -1,63 +0,0 @@
import { NostrEvent, nip19 } from "nostr-tools";
import emojiRegex from "emoji-regex";
import { truncatedId } from "./event";
import { ProfileContent } from "applesauce-core/helpers";
/** @deprecated use ProfileContent instead */
export type Kind0ParsedContent = {
pubkey?: string;
name?: string;
display_name?: string;
displayName?: string;
about?: string;
/** @deprecated */
image?: string;
picture?: string;
banner?: string;
website?: string;
lud16?: string;
lud06?: string;
nip05?: string;
};
/** @deprecated use getProfileContent instead */
export function parseMetadataContent(event: NostrEvent): Kind0ParsedContent {
try {
const metadata = JSON.parse(event.content) as Kind0ParsedContent;
metadata.pubkey = event.pubkey;
// ensure nip05 is a string
if (metadata.nip05 && typeof metadata.nip05 !== "string") metadata.nip05 = String(metadata.nip05);
// fix user website
if (metadata.website) metadata.website = fixWebsiteUrl(metadata.website);
return metadata;
} catch (e) {}
return {};
}
export function getSearchNames(profile: ProfileContent) {
if (!profile) return [];
return [profile.displayName, profile.display_name, profile.name].filter(Boolean) as string[];
}
const matchEmoji = emojiRegex();
export function getDisplayName(metadata: Kind0ParsedContent | undefined, pubkey: string, removeEmojis = false) {
let displayName = metadata?.displayName || metadata?.display_name || metadata?.name;
if (displayName) {
if (removeEmojis) displayName = displayName.replaceAll(matchEmoji, "");
return displayName;
}
return truncatedId(nip19.npubEncode(pubkey));
}
export function fixWebsiteUrl(website: string) {
if (website.match(/^http?s:\/\//)) {
return website;
}
return "https://" + website;
}

View File

@ -4,12 +4,12 @@ export const FLARE_VIDEO_KIND = 34235;
export function getVideoTitle(video: NostrEvent) {
const title = video.tags.find((t) => t[0] === "title")?.[1];
if (!title) throw new Error("missing title");
if (!title) throw new Error("Missing title");
return title;
}
export function getVideoUrl(video: NostrEvent) {
const url = video.tags.find((t) => t[0] === "url")?.[1];
if (!url) throw new Error("missing url");
if (!url) throw new Error("Missing url");
return url;
}
export function getVideoSummary(video: NostrEvent) {

View File

@ -1,8 +1,6 @@
import { bech32 } from "@scure/base";
import {} from "nostr-tools/nip57";
import { isETag, isPTag, NostrEvent } from "../../types/nostr-event";
import { utils } from "nostr-tools";
import { getZapPayment, ProfileContent } from "applesauce-core/helpers";
import { NostrEvent, utils } from "nostr-tools";
import { getZapPayment, isETag, isPTag, ProfileContent } from "applesauce-core/helpers";
// based on https://github.com/nbd-wtf/nostr-tools/blob/master/nip57.ts
export async function getZapEndpoint(metadata: ProfileContent): Promise<null | string> {

View File

@ -6,7 +6,7 @@ import { getPubkeysFromList } from "../../helpers/nostr/lists";
import useCurrentAccount from "../../hooks/use-current-account";
import { PubkeyGraph } from "../../classes/pubkey-graph";
import replaceableEventsService from "../../services/replaceable-events";
import { COMMON_CONTACT_RELAY } from "../../const";
import { COMMON_CONTACT_RELAYS } from "../../const";
import { eventStore } from "../../services/event-store";
export function loadSocialGraph(
@ -40,7 +40,11 @@ export function loadSocialGraph(
if (contacts) {
handleEvent(contacts);
} else {
replaceableEventsService.requestEvent(relay ? [relay, COMMON_CONTACT_RELAY] : [COMMON_CONTACT_RELAY], kind, pubkey);
replaceableEventsService.requestEvent(
relay ? [relay, ...COMMON_CONTACT_RELAYS] : COMMON_CONTACT_RELAYS,
kind,
pubkey,
);
// wait for event to load
const sub = eventStore.replaceable(kind, pubkey).subscribe((e) => {

View File

@ -20,7 +20,7 @@ import { PropsWithChildren, createContext, useCallback, useContext, useEffect, u
import dayjs from "dayjs";
import { useInterval } from "react-use";
import { getDisplayName } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import useUserProfile from "../../hooks/use-user-profile";
import useCurrentAccount from "../../hooks/use-current-account";
import {

View File

@ -3,7 +3,7 @@ import _throttle from "lodash.throttle";
import { combineLatest, distinct, filter } from "rxjs";
import { USER_BLOSSOM_SERVER_LIST_KIND } from "blossom-client-sdk";
import { COMMON_CONTACT_RELAY } from "../const";
import { COMMON_CONTACT_RELAYS } from "../const";
import { logger } from "../helpers/debug";
import accountService from "./account";
import clientRelaysService from "./client-relays";
@ -24,7 +24,7 @@ function downloadEvents(account: Account) {
};
log("Loading outboxes");
requestReplaceable([...relays, COMMON_CONTACT_RELAY], kinds.RelayList);
requestReplaceable([...relays, ...COMMON_CONTACT_RELAYS], kinds.RelayList);
const mailboxesSub = queryStore.mailboxes(account.pubkey).subscribe((mailboxes) => {
log("Loading user information");
@ -35,7 +35,7 @@ function downloadEvents(account: Account) {
log("Loading contacts list");
replaceableEventsService.requestEvent(
[...clientRelaysService.readRelays.value, COMMON_CONTACT_RELAY],
[...clientRelaysService.readRelays.value, ...COMMON_CONTACT_RELAYS],
kinds.Contacts,
account.pubkey,
undefined,

View File

@ -4,7 +4,7 @@ import { from } from "rxjs";
import { filter, bufferTime, concatMap, mergeWith, shareReplay, map, scan } from "rxjs/operators";
import { getProfileContent, isFromCache } from "applesauce-core/helpers";
import { getSearchNames } from "../helpers/nostr/user-metadata";
import { getSearchNames } from "../helpers/nostr/profile";
import db from "./db";
import { eventStore } from "./event-store";
import { logger } from "../helpers/debug";

View File

@ -3,7 +3,7 @@ import { Link as RouterLink } from "react-router-dom";
import { nip19 } from "nostr-tools";
import useUserProfile from "../../../../hooks/use-user-profile";
import { getDisplayName } from "../../../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../../../helpers/nostr/profile";
import { AddressPointer } from "nostr-tools/nip19";
import useDVMMetadata from "../../../../hooks/use-dvm-metadata";

View File

@ -10,12 +10,11 @@ import {
Link,
Textarea,
} from "@chakra-ui/react";
import dayjs from "dayjs";
import { useForm } from "react-hook-form";
import { ProfileContent, unixNow } from "applesauce-core/helpers";
import { ExternalLinkIcon } from "../../components/icons";
import { isLNURL } from "../../helpers/lnurl";
import { Kind0ParsedContent } from "../../helpers/nostr/user-metadata";
import { useReadRelays } from "../../hooks/use-client-relays";
import useCurrentAccount from "../../hooks/use-current-account";
import useUserProfile from "../../hooks/use-user-profile";
@ -23,7 +22,7 @@ import dnsIdentityService from "../../services/dns-identity";
import { DraftNostrEvent } from "../../types/nostr-event";
import lnurlMetadataService from "../../services/lnurl-metadata";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { COMMON_CONTACT_RELAY } from "../../const";
import { COMMON_CONTACT_RELAYS } from "../../const";
import { usePublishEvent } from "../../providers/global/publish-provider";
const isEmail =
@ -234,7 +233,7 @@ export const ProfileEditView = () => {
);
const handleSubmit = async (data: FormData) => {
const newMetadata: Kind0ParsedContent = {
const newMetadata: ProfileContent = {
name: data.username,
picture: data.picture,
banner: data.banner,
@ -253,13 +252,13 @@ export const ProfileEditView = () => {
}
const draft: DraftNostrEvent = {
created_at: dayjs().unix(),
created_at: unixNow(),
kind: 0,
content: JSON.stringify({ ...metadata, ...newMetadata }),
tags: [],
};
await publish("Update Profile", draft, [COMMON_CONTACT_RELAY]);
await publish("Update Profile", draft, COMMON_CONTACT_RELAYS);
};
return <MetadataForm defaultValues={defaultValues} onSubmit={handleSubmit} />;

View File

@ -2,6 +2,7 @@ import { useCallback } from "react";
import { Flex, Heading, IconButton, Link, Text } from "@chakra-ui/react";
import { CloseIcon } from "@chakra-ui/icons";
import { Link as RouterLink } from "react-router-dom";
import { kinds } from "nostr-tools";
import RequireCurrentAccount from "../../../providers/route/require-current-account";
import useUserMailboxes from "../../../hooks/use-user-mailboxes";
@ -12,19 +13,18 @@ import { RelayMode } from "../../../classes/relay";
import { NostrEvent } from "../../../types/nostr-event";
import useAsyncErrorHandler from "../../../hooks/use-async-error-handler";
import { usePublishEvent } from "../../../providers/global/publish-provider";
import { COMMON_CONTACT_RELAY } from "../../../const";
import BackButton from "../../../components/router/back-button";
import { addRelayModeToMailbox, removeRelayModeFromMailbox } from "../../../helpers/nostr/mailbox";
import AddRelayForm from "../app/add-relay-form";
import DebugEventButton from "../../../components/debug-modal/debug-event-button";
import useReplaceableEvent from "../../../hooks/use-replaceable-event";
import { kinds } from "nostr-tools";
import { COMMON_CONTACT_RELAYS } from "../../../const";
function RelayLine({ relay, mode, list }: { relay: string; mode: RelayMode; list?: NostrEvent }) {
const publish = usePublishEvent();
const remove = useAsyncErrorHandler(async () => {
const draft = removeRelayModeFromMailbox(list, relay, mode);
await publish("Remove relay", draft, [COMMON_CONTACT_RELAY]);
await publish("Remove relay", draft, COMMON_CONTACT_RELAYS);
}, [relay, mode, list, publish]);
return (
@ -55,7 +55,7 @@ function MailboxesPage() {
const addRelay = useCallback(
async (relay: string, mode: RelayMode) => {
const draft = addRelayModeToMailbox(event ?? undefined, relay, mode);
await publish("Add Relay", draft, [COMMON_CONTACT_RELAY]);
await publish("Add Relay", draft, COMMON_CONTACT_RELAYS);
},
[event],
);

View File

@ -1,7 +1,7 @@
import { CloseIcon } from "@chakra-ui/icons";
import { Box, IconButton, Text } from "@chakra-ui/react";
import { getDisplayName } from "../../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../../helpers/nostr/profile";
import useUserProfile from "../../../hooks/use-user-profile";
import accountService from "../../../services/account";
import UserAvatar from "../../../components/user/user-avatar";

View File

@ -2,13 +2,12 @@ import { useEffect, useState } from "react";
import { generateSecretKey, finalizeEvent, kinds } from "nostr-tools";
import { Avatar, Button, Flex, Heading, Text, useToast } from "@chakra-ui/react";
import { bytesToHex } from "@noble/hashes/utils";
import dayjs from "dayjs";
import { ProfileContent, unixNow } from "applesauce-core/helpers";
import { Kind0ParsedContent } from "../../helpers/nostr/user-metadata";
import { containerProps } from "./common";
import { nostrBuildUploadImage } from "../../helpers/media-upload/nostr-build";
import accountService from "../../services/account";
import { COMMON_CONTACT_RELAY } from "../../const";
import { COMMON_CONTACT_RELAYS } from "../../const";
import { DraftNostrEvent } from "../../types/nostr-event";
import { usePublishEvent } from "../../providers/global/publish-provider";
import NsecAccount from "../../classes/accounts/nsec-account";
@ -20,7 +19,7 @@ export default function CreateStep({
onBack,
onSubmit,
}: {
metadata: Kind0ParsedContent;
metadata: ProfileContent;
relays: string[];
profileImage?: File;
onBack: () => void;
@ -52,14 +51,14 @@ export default function CreateStep({
const kind0 = finalizeEvent(
{
content: JSON.stringify({ ...metadata, picture: uploaded?.url }),
created_at: dayjs().unix(),
created_at: unixNow(),
kind: kinds.Metadata,
tags: [],
},
hex,
);
await publish("Create Profile", kind0, [...relays, COMMON_CONTACT_RELAY]);
await publish("Create Profile", kind0, [...relays, ...COMMON_CONTACT_RELAYS]);
// login
const account = NsecAccount.newKey();
@ -71,7 +70,7 @@ export default function CreateStep({
kind: kinds.RelayList,
content: "",
tags: relays.map((url) => ["r", url]),
created_at: dayjs().unix(),
created_at: unixNow(),
};
const signed = finalizeEvent(draft, hex);
await publish("Set Mailbox Relays", signed, relays);

View File

@ -1,12 +1,12 @@
import { Box, Button, Card, Flex, Heading, Text } from "@chakra-ui/react";
import { Button, Card, Flex, Heading, Text } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { ProfileContent } from "applesauce-core/helpers";
import { useAsync } from "react-use";
import UserAvatarLink from "../../components/user/user-avatar-link";
import UserLink from "../../components/user/user-link";
import { containerProps } from "./common";
import { UserFollowButton } from "../../components/user/user-follow-button";
import { Kind0ParsedContent } from "../../helpers/nostr/user-metadata";
import UserDnsIdentity from "../../components/user/user-dns-identity";
type TrendingApi = {
@ -24,7 +24,7 @@ type TrendingApi = {
function About({ profile }: { profile: { content: string } }) {
const { value: metadata, error } = useAsync(
async () => JSON.parse(profile.content) as Kind0ParsedContent,
async () => JSON.parse(profile.content) as ProfileContent,
[profile.content],
);
return metadata ? <Text>{metadata.about}</Text> : null;

View File

@ -1,8 +1,8 @@
import { useState } from "react";
import { Flex } from "@chakra-ui/react";
import { useNavigate, useParams } from "react-router-dom";
import { ProfileContent } from "applesauce-core/helpers";
import { Kind0ParsedContent } from "../../helpers/nostr/user-metadata";
import NameStep from "./name-step";
import ProfileImageStep from "./profile-image-step";
import RelayStep from "./relay-step";
@ -14,7 +14,7 @@ export default function SignupView() {
const step = useParams().step || "name";
const navigate = useNavigate();
const [metadata, setMetadata] = useState<Kind0ParsedContent>({});
const [metadata, setMetadata] = useState<ProfileContent>({});
const [profileImage, setProfileImage] = useState<File>();
const [relays, setRelays] = useState<string[]>([]);
const [secretKey, setSecretKey] = useState("");

View File

@ -1,11 +1,11 @@
import { Button, Flex, Heading, Input, Text, Textarea } from "@chakra-ui/react";
import { useForm } from "react-hook-form";
import { ProfileContent } from "applesauce-core/helpers";
import { Link as RouterLink, useLocation } from "react-router-dom";
import { Kind0ParsedContent } from "../../helpers/nostr/user-metadata";
import { AppIcon, containerProps } from "./common";
export default function NameStep({ onSubmit }: { onSubmit: (metadata: Kind0ParsedContent) => void }) {
export default function NameStep({ onSubmit }: { onSubmit: (metadata: ProfileContent) => void }) {
const location = useLocation();
const { register, handleSubmit, formState } = useForm({
defaultValues: {

View File

@ -28,7 +28,7 @@ import {
import { Outlet, useMatches, useNavigate } from "react-router-dom";
import useUserProfile from "../../hooks/use-user-profile";
import { getDisplayName } from "../../helpers/nostr/user-metadata";
import { getDisplayName } from "../../helpers/nostr/profile";
import { useAppTitle } from "../../hooks/use-app-title";
import { useReadRelays } from "../../hooks/use-client-relays";
import relayScoreboardService from "../../services/relay-scoreboard";