add popular relays view

This commit is contained in:
hzrd149 2023-09-27 14:32:46 -05:00
parent 3c841591be
commit a1a0e331d8
6 changed files with 130 additions and 7 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add popular relays view

View File

@ -59,6 +59,7 @@ import CommunitiesHomeView from "./views/communities";
import CommunityFindByNameView from "./views/community/find-by-name";
import CommunityView from "./views/community/index";
import StreamModerationView from "./views/tools/stream-moderation";
import PopularRelaysView from "./views/relays/popular";
const NetworkView = React.lazy(() => import("./views/tools/network"));
const NetworkGraphView = React.lazy(() => import("./views/tools/network-mute-graph"));
@ -160,8 +161,14 @@ const router = createHashRouter([
element: <NoteView />,
},
{ path: "settings", element: <SettingsView /> },
{ path: "relays/reviews", element: <RelayReviewsView /> },
{ path: "relays", element: <RelaysView /> },
{
path: "relays",
children: [
{ path: "", element: <RelaysView /> },
{ path: "popular", element: <PopularRelaysView /> },
{ path: "reviews", element: <RelayReviewsView /> },
],
},
{ path: "r/:relay", element: <RelayView /> },
{ path: "notifications", element: <NotificationsView /> },
{ path: "search", element: <SearchView /> },

View File

@ -28,7 +28,7 @@ import { Link as RouterLink } from "react-router-dom";
import { useRelayInfo } from "../../../hooks/use-relay-info";
import { RelayFavicon } from "../../../components/relay-favicon";
import { CodeIcon, RepostIcon } from "../../../components/icons";
import { CodeIcon } from "../../../components/icons";
import { UserLink } from "../../../components/user-link";
import { UserAvatar } from "../../../components/user-avatar";
import { useClientRelays } from "../../../hooks/use-client-relays";
@ -160,7 +160,7 @@ export default function RelayCard({ url, ...props }: { url: string } & Omit<Card
<>
<Card variant="outline" {...props}>
<CardHeader display="flex" gap="2" alignItems="center" p="2">
<RelayFavicon relay={url} size="xs" />
<RelayFavicon relay={url} size="sm" />
<Heading size="md" isTruncated>
<RouterLink to={`/r/${encodeURIComponent(url)}`}>{url}</RouterLink>
<RelayPaidTag url={url} />

View File

@ -25,6 +25,7 @@ export default function RelaysView() {
.filter((r) => !clientRelays.includes(r.url))
.map((r) => r.url)
.filter(safeRelayUrl);
const { value: onlineRelays = [] } = useAsync(async () =>
fetch("https://api.nostr.watch/v1/online").then((res) => res.json() as Promise<string[]>),
);
@ -42,6 +43,9 @@ export default function RelaysView() {
<Flex alignItems="center" gap="2" wrap="wrap">
<Input type="search" placeholder="search" value={search} onChange={(e) => setSearch(e.target.value)} w="auto" />
<Spacer />
<Button as={RouterLink} to="/relays/popular">
Popular Relays
</Button>
<Button as={RouterLink} to="/relays/reviews">
Browse Reviews
</Button>
@ -57,7 +61,7 @@ export default function RelaysView() {
))}
</SimpleGrid>
{discoveredRelays && !isSearching && (
{discoveredRelays.length > 0 && !isSearching && (
<>
<Divider />
<Heading size="lg">Discovered Relays</Heading>

View File

@ -0,0 +1,106 @@
import {
AvatarGroup,
Button,
Card,
CardBody,
CardHeader,
Flex,
Heading,
LinkBox,
LinkOverlay,
SimpleGrid,
Text,
} from "@chakra-ui/react";
import { memo } from "react";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import VerticalPageLayout from "../../components/vertical-page-layout";
import { getPubkeysFromList } from "../../helpers/nostr/lists";
import { useClientRelays, useReadRelayUrls } from "../../hooks/use-client-relays";
import { useCurrentAccount } from "../../hooks/use-current-account";
import useSubjects from "../../hooks/use-subjects";
import useUserContactList from "../../hooks/use-user-contact-list";
import RequireCurrentAccount from "../../providers/require-current-account";
import userRelaysService from "../../services/user-relays";
import { NostrEvent } from "../../types/nostr-event";
import { RelayFavicon } from "../../components/relay-favicon";
import { ArrowLeftSIcon } from "../../components/icons";
import { UserAvatar } from "../../components/user-avatar";
import { RelayMetadata } from "./components/relay-card";
function usePopularContactsRelays(list?: NostrEvent) {
const readRelays = useReadRelayUrls();
const subs = list ? getPubkeysFromList(list).map((p) => userRelaysService.requestRelays(p.pubkey, readRelays)) : [];
const contactsRelays = useSubjects(subs);
const relayScore: Record<string, string[]> = {};
for (const { relays, pubkey } of contactsRelays) {
for (const { url } of relays) {
relayScore[url] = relayScore[url] || [];
relayScore[url].push(pubkey);
}
}
const relayUrls = Array.from(Object.entries(relayScore)).map(([url, pubkeys]) => ({ url, pubkeys }));
return relayUrls.sort((a, b) => b.pubkeys.length - a.pubkeys.length);
}
const RelayCard = memo(({ url, pubkeys }: { url: string; pubkeys: string[] }) => {
return (
<Card variant="outline" as={LinkBox}>
<CardHeader px="2" pt="2" pb="0" display="flex" gap="2" alignItems="center">
<RelayFavicon relay={url} size="sm" />
<Heading size="md" isTruncated>
<LinkOverlay as={RouterLink} to={`/r/${encodeURIComponent(url)}`}>
{url}
</LinkOverlay>
</Heading>
</CardHeader>
<CardBody p="2">
<RelayMetadata url={url} />
<Text>Used by {pubkeys.length} contacts:</Text>
<AvatarGroup size="sm" max={10}>
{pubkeys.map((pubkey) => (
<UserAvatar key={pubkey} pubkey={pubkey} />
))}
</AvatarGroup>
</CardBody>
</Card>
);
});
function PopularRelaysPage() {
const navigate = useNavigate();
const account = useCurrentAccount();
const contacts = useUserContactList(account?.pubkey);
const clientRelays = useClientRelays().map((r) => r.url);
const popularRelays = usePopularContactsRelays(contacts).filter(
(r) => !clientRelays.includes(r.url) && r.pubkeys.length > 1,
);
return (
<VerticalPageLayout>
<Flex gap="2" alignItems="center">
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon />}>
Back
</Button>
<Heading size="md">Popular Relays</Heading>
</Flex>
<SimpleGrid columns={[1, 1, 1, 2, 3]} spacing="2">
{popularRelays.map(({ url, pubkeys }) => (
<RelayCard url={url} pubkeys={pubkeys} key={url} />
))}
</SimpleGrid>
</VerticalPageLayout>
);
}
export default function PopularRelaysView() {
return (
<RequireCurrentAccount>
<PopularRelaysPage />
</RequireCurrentAccount>
);
}

View File

@ -1,4 +1,4 @@
import { Button, Flex } from "@chakra-ui/react";
import { Button, Flex, Heading } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
@ -35,11 +35,12 @@ function RelayReviewsPage() {
return (
<IntersectionObserverProvider callback={callback}>
<VerticalPageLayout>
<Flex gap="2">
<Flex gap="2" alignItems="center">
<Button onClick={() => navigate(-1)} leftIcon={<ArrowLeftSIcon />}>
Back
</Button>
<PeopleListSelection />
<Heading size="md">Relay Reviews</Heading>
</Flex>
{reviews.map((event) => (
<RelayReviewNote key={event.id} event={event} />