mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-12 05:39:18 +02:00
add relay view
This commit is contained in:
parent
e24e55c8d9
commit
fa30250a20
5
.changeset/good-dryers-thank.md
Normal file
5
.changeset/good-dryers-thank.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"nostrudel": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add relay view
|
@ -36,6 +36,7 @@ import UserStreamsTab from "./views/user/streams";
|
|||||||
import { PageProviders } from "./providers";
|
import { PageProviders } from "./providers";
|
||||||
import RelaysView from "./views/relays";
|
import RelaysView from "./views/relays";
|
||||||
import RelayReviewsView from "./views/relays/reviews";
|
import RelayReviewsView from "./views/relays/reviews";
|
||||||
|
import RelayView from "./views/relays/relay";
|
||||||
|
|
||||||
const StreamsView = React.lazy(() => import("./views/streams"));
|
const StreamsView = React.lazy(() => import("./views/streams"));
|
||||||
const StreamView = React.lazy(() => import("./views/streams/stream"));
|
const StreamView = React.lazy(() => import("./views/streams/stream"));
|
||||||
@ -107,6 +108,7 @@ const router = createHashRouter([
|
|||||||
{ path: "settings", element: <SettingsView /> },
|
{ path: "settings", element: <SettingsView /> },
|
||||||
{ path: "relays/reviews", element: <RelayReviewsView /> },
|
{ path: "relays/reviews", element: <RelayReviewsView /> },
|
||||||
{ path: "relays", element: <RelaysView /> },
|
{ path: "relays", element: <RelaysView /> },
|
||||||
|
{ path: "r/:relay", element: <RelayView /> },
|
||||||
{ path: "notifications", element: <NotificationsView /> },
|
{ path: "notifications", element: <NotificationsView /> },
|
||||||
{ path: "search", element: <SearchView /> },
|
{ path: "search", element: <SearchView /> },
|
||||||
{ path: "dm", element: <DirectMessagesView /> },
|
{ path: "dm", element: <DirectMessagesView /> },
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import {
|
import {
|
||||||
Avatar,
|
|
||||||
Box,
|
|
||||||
Flex,
|
Flex,
|
||||||
FlexProps,
|
FlexProps,
|
||||||
IconButton,
|
Link,
|
||||||
Modal,
|
Modal,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
ModalCloseButton,
|
ModalCloseButton,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
|
Tag,
|
||||||
Text,
|
Text,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
|
||||||
import { RelayFavicon } from "./relay-favicon";
|
import { RelayFavicon } from "./relay-favicon";
|
||||||
import relayScoreboardService from "../services/relay-scoreboard";
|
import relayScoreboardService from "../services/relay-scoreboard";
|
||||||
|
|
||||||
@ -34,18 +35,27 @@ export function RelayIconStack({ relays, maxRelays, ...props }: { relays: string
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Modal isOpen={isOpen} onClose={onClose}>
|
<Modal isOpen={isOpen} onClose={onClose} size="lg">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader>Relays</ModalHeader>
|
<ModalHeader px="4" pt="4" pb="2">
|
||||||
|
Seen on relays:
|
||||||
|
</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody>
|
<ModalBody px="4" pb="4" pt="0">
|
||||||
<Flex direction="column" gap="1">
|
<Flex gap="2" wrap="wrap">
|
||||||
{topRelays.map((url) => (
|
{topRelays.map((url) => (
|
||||||
<Flex key={url}>
|
<Tag
|
||||||
<RelayFavicon relay={url} size="2xs" mr="2" />
|
key={url}
|
||||||
<Text>{url}</Text>
|
as={RouterLink}
|
||||||
</Flex>
|
p="2"
|
||||||
|
fontWeight="bold"
|
||||||
|
fontSize="md"
|
||||||
|
to={`/r/${encodeURIComponent(url)}`}
|
||||||
|
>
|
||||||
|
<RelayFavicon relay={url} size="xs" mr="2" />
|
||||||
|
{url}
|
||||||
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonProps,
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardProps,
|
CardProps,
|
||||||
Code,
|
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -19,8 +18,6 @@ import {
|
|||||||
ModalHeader,
|
ModalHeader,
|
||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
ModalProps,
|
ModalProps,
|
||||||
Spacer,
|
|
||||||
Text,
|
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
@ -41,36 +38,6 @@ import styled from "@emotion/styled";
|
|||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
import RawJson from "../../../components/debug-modals/raw-json";
|
import RawJson from "../../../components/debug-modals/raw-json";
|
||||||
|
|
||||||
function RelayReviewsModal({ relay, ...props }: { relay: string } & Omit<ModalProps, "children">) {
|
|
||||||
const readRelays = useReadRelayUrls();
|
|
||||||
const timeline = useTimelineLoader(`${relay}-reviews`, readRelays, {
|
|
||||||
kinds: [1985],
|
|
||||||
"#r": [relay],
|
|
||||||
"#l": ["review/relay"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const events = useSubject(timeline.timeline);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal {...props}>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalContent>
|
|
||||||
<ModalHeader p="4" pb="0">
|
|
||||||
{relay} reviews
|
|
||||||
</ModalHeader>
|
|
||||||
<ModalCloseButton />
|
|
||||||
<ModalBody px="4" pt="0" pb="4">
|
|
||||||
<Flex gap="2" direction="column">
|
|
||||||
{events.map((event) => (
|
|
||||||
<RelayReviewNote key={event.id} event={event} hideUrl />
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
</ModalBody>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const B = styled.span`
|
const B = styled.span`
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
`;
|
`;
|
||||||
@ -100,7 +67,7 @@ export function RelayMetadata({ url }: { url: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RelayJoinAction({ url }: { url: string }) {
|
export function RelayJoinAction({ url, ...props }: { url: string } & Omit<ButtonProps, "children" | "onClick">) {
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
const clientRelays = useClientRelays();
|
const clientRelays = useClientRelays();
|
||||||
const joined = clientRelays.some((r) => r.url === url);
|
const joined = clientRelays.some((r) => r.url === url);
|
||||||
@ -111,7 +78,7 @@ export function RelayJoinAction({ url }: { url: string }) {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => clientRelaysService.removeRelay(url)}
|
onClick={() => clientRelaysService.removeRelay(url)}
|
||||||
isDisabled={!account}
|
isDisabled={!account}
|
||||||
size="sm"
|
{...props}
|
||||||
>
|
>
|
||||||
Leave
|
Leave
|
||||||
</Button>
|
</Button>
|
||||||
@ -120,26 +87,19 @@ export function RelayJoinAction({ url }: { url: string }) {
|
|||||||
colorScheme="green"
|
colorScheme="green"
|
||||||
onClick={() => clientRelaysService.addRelay(url, RelayMode.ALL)}
|
onClick={() => clientRelaysService.addRelay(url, RelayMode.ALL)}
|
||||||
isDisabled={!account}
|
isDisabled={!account}
|
||||||
size="sm"
|
{...props}
|
||||||
>
|
>
|
||||||
Join
|
Join
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DebugButton({ url, ...props }: { url: string } & Omit<IconButtonProps, "icon" | "aria-label">) {
|
export function RelayDebugButton({ url, ...props }: { url: string } & Omit<IconButtonProps, "icon" | "aria-label">) {
|
||||||
const { info } = useRelayInfo(url);
|
const { info } = useRelayInfo(url);
|
||||||
const debugModal = useDisclosure();
|
const debugModal = useDisclosure();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton icon={<CodeIcon />} aria-label="Show JSON" onClick={debugModal.onToggle} variant="ghost" {...props} />
|
||||||
icon={<CodeIcon />}
|
|
||||||
aria-label="Show JSON"
|
|
||||||
onClick={debugModal.onToggle}
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
{debugModal.isOpen && (
|
{debugModal.isOpen && (
|
||||||
<Modal isOpen onClose={debugModal.onClose} size="4xl">
|
<Modal isOpen onClose={debugModal.onClose} size="4xl">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
@ -157,30 +117,22 @@ export function DebugButton({ url, ...props }: { url: string } & Omit<IconButton
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RelayCard({ url, ...props }: { url: string } & Omit<CardProps, "children">) {
|
export default function RelayCard({ url, ...props }: { url: string } & Omit<CardProps, "children">) {
|
||||||
const reviewsModal = useDisclosure();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card {...props}>
|
<Card {...props}>
|
||||||
<CardHeader display="flex" gap="2" alignItems="center" p="2">
|
<CardHeader display="flex" gap="2" alignItems="center" p="2">
|
||||||
<RelayFavicon relay={url} size="xs" />
|
<RelayFavicon relay={url} size="xs" />
|
||||||
<Heading size="md" isTruncated>
|
<Heading size="md" isTruncated>
|
||||||
{url}
|
<RouterLink to={`/r/${encodeURIComponent(url)}`}>{url}</RouterLink>
|
||||||
</Heading>
|
</Heading>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody px="2" py="0" display="flex" flexDirection="column" gap="2">
|
<CardBody px="2" py="0" display="flex" flexDirection="column" gap="2">
|
||||||
<RelayMetadata url={url} />
|
<RelayMetadata url={url} />
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter p="2" as={Flex} gap="2">
|
<CardFooter p="2" as={Flex} gap="2">
|
||||||
<RelayJoinAction url={url} />
|
<RelayJoinAction url={url} size="sm" />
|
||||||
<Button onClick={reviewsModal.onOpen} size="sm">
|
|
||||||
Reviews
|
|
||||||
</Button>
|
|
||||||
<Button as={RouterLink} to={`/global?relay=${url}`} size="sm">
|
|
||||||
Notes
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<DebugButton url={url} ml="auto" />
|
<RelayDebugButton url={url} ml="auto" size="sm" />
|
||||||
<Button
|
<Button
|
||||||
as="a"
|
as="a"
|
||||||
href={`https://nostr.watch/relay/${new URL(url).host}`}
|
href={`https://nostr.watch/relay/${new URL(url).host}`}
|
||||||
@ -192,7 +144,6 @@ export default function RelayCard({ url, ...props }: { url: string } & Omit<Card
|
|||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
{reviewsModal.isOpen && <RelayReviewsModal isOpen onClose={reviewsModal.onClose} relay={url} size="2xl" />}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { Card, CardBody, CardHeader, Text } from "@chakra-ui/react";
|
import { Card, CardBody, CardHeader, Link, Text } from "@chakra-ui/react";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
|
||||||
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
import { UserAvatarLink } from "../../../components/user-avatar-link";
|
||||||
import { UserLink } from "../../../components/user-link";
|
import { UserLink } from "../../../components/user-link";
|
||||||
@ -30,7 +31,13 @@ export default function RelayReviewNote({ event, hideUrl }: { event: NostrEvent;
|
|||||||
<Text ml="auto">{dayjs.unix(event.created_at).fromNow()}</Text>
|
<Text ml="auto">{dayjs.unix(event.created_at).fromNow()}</Text>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody p="2">
|
<CardBody p="2">
|
||||||
{!hideUrl && <Metadata name="URL">{url}</Metadata>}
|
{!hideUrl && url && (
|
||||||
|
<Metadata name="URL">
|
||||||
|
<Link as={RouterLink} to={`/r/${encodeURIComponent(url)}`}>
|
||||||
|
{url}
|
||||||
|
</Link>
|
||||||
|
</Metadata>
|
||||||
|
)}
|
||||||
{rating && <StarRating quality={rating.quality} color="yellow.400" mb="1" />}
|
{rating && <StarRating quality={rating.quality} color="yellow.400" mb="1" />}
|
||||||
<NoteContents event={event} />
|
<NoteContents event={event} />
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
139
src/views/relays/relay.tsx
Normal file
139
src/views/relays/relay.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { Box, Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs, Tag, Tooltip } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
import { safeRelayUrl } from "../../helpers/url";
|
||||||
|
import { useRelayInfo } from "../../hooks/use-relay-info";
|
||||||
|
import { RelayDebugButton, RelayJoinAction, RelayMetadata } from "./components/relay-card";
|
||||||
|
import useSubject from "../../hooks/use-subject";
|
||||||
|
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||||
|
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||||
|
import RelayReviewNote from "./components/relay-review-note";
|
||||||
|
|
||||||
|
// copied from github
|
||||||
|
const NIP_NAMES: Record<string, string> = {
|
||||||
|
"01": "Basic protocol",
|
||||||
|
"02": "Contact List and Petnames",
|
||||||
|
"03": "OpenTimestamps Attestations for Events",
|
||||||
|
"04": "Encrypted Direct Message",
|
||||||
|
"05": "Mapping Nostr keys to DNS-based internet identifiers",
|
||||||
|
"06": "Basic key derivation from mnemonic seed phrase",
|
||||||
|
"07": "window.nostr capability for web browsers",
|
||||||
|
"08": "Handling Mentions",
|
||||||
|
"09": "Event Deletion",
|
||||||
|
"10": "Conventions for clients' use of e and p tags in text events",
|
||||||
|
"11": "Relay Information Document",
|
||||||
|
"12": "Generic Tag Queries",
|
||||||
|
"13": "Proof of Work",
|
||||||
|
"14": "Subject tag in text events",
|
||||||
|
"15": "Nostr Marketplace",
|
||||||
|
"16": "Event Treatment",
|
||||||
|
"18": "Reposts",
|
||||||
|
"19": "bech32-encoded entities",
|
||||||
|
"20": "Command Results",
|
||||||
|
"21": "nostr: URI scheme",
|
||||||
|
"22": "Event created_at Limits",
|
||||||
|
"23": "Long-form Content",
|
||||||
|
"25": "Reactions",
|
||||||
|
"26": "Delegated Event Signing",
|
||||||
|
"27": "Text Note References",
|
||||||
|
"28": "Public Chat",
|
||||||
|
"30": "Custom Emoji",
|
||||||
|
"31": "Dealing with Unknown Events",
|
||||||
|
"32": "Labeling",
|
||||||
|
"33": "Parameterized Replaceable Events",
|
||||||
|
"36": "Sensitive Content",
|
||||||
|
"39": "External Identities in Profiles",
|
||||||
|
"40": "Expiration Timestamp",
|
||||||
|
"42": "Authentication of clients to relays",
|
||||||
|
"45": "Counting results",
|
||||||
|
"46": "Nostr Connect",
|
||||||
|
"47": "Wallet Connect",
|
||||||
|
"50": "Keywords filter",
|
||||||
|
"51": "Lists",
|
||||||
|
"52": "Calendar Events",
|
||||||
|
"53": "Live Activities",
|
||||||
|
"56": "Reporting",
|
||||||
|
"57": "Lightning Zaps",
|
||||||
|
"58": "Badges",
|
||||||
|
"65": "Relay List Metadata",
|
||||||
|
"78": "Application-specific data",
|
||||||
|
"89": "Recommended Application Handlers",
|
||||||
|
"94": "File Metadata",
|
||||||
|
"98": "HTTP Auth",
|
||||||
|
"99": "Classified Listings",
|
||||||
|
};
|
||||||
|
|
||||||
|
function NipTag({ nip }: { nip: number }) {
|
||||||
|
const nipStr = String(nip).padStart(2, "0");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip label={NIP_NAMES[nipStr]}>
|
||||||
|
<Tag as="a" target="_blank" href={`https://github.com/nostr-protocol/nips/blob/master/${nipStr}.md`}>
|
||||||
|
NIP-{nip}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RelayReviews({ relay }: { relay: string }) {
|
||||||
|
const readRelays = useReadRelayUrls();
|
||||||
|
const timeline = useTimelineLoader(`${relay}-reviews`, readRelays, {
|
||||||
|
kinds: [1985],
|
||||||
|
"#r": [relay],
|
||||||
|
"#l": ["review/relay"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const events = useSubject(timeline.timeline);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction="column" gap="2">
|
||||||
|
{events.map((event) => (
|
||||||
|
<RelayReviewNote key={event.id} event={event} hideUrl />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RelayPage({ relay }: { relay: string }) {
|
||||||
|
const { info } = useRelayInfo(relay);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction="column" alignItems="stretch" gap="2" py="2">
|
||||||
|
<Flex gap="2" alignItems="center">
|
||||||
|
<Heading>{relay}</Heading>
|
||||||
|
<RelayDebugButton url={relay} ml="auto" />
|
||||||
|
<RelayJoinAction url={relay} />
|
||||||
|
</Flex>
|
||||||
|
<RelayMetadata url={relay} />
|
||||||
|
<Flex gap="2">
|
||||||
|
{info?.supported_nips?.map((nip) => (
|
||||||
|
<NipTag key={nip} nip={nip} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
<Tabs display="flex" flexDirection="column" flexGrow="1" isLazy colorScheme="brand">
|
||||||
|
<TabList overflowX="auto" overflowY="hidden" flexShrink={0}>
|
||||||
|
<Tab>Reviews</Tab>
|
||||||
|
<Tab isDisabled>Notes</Tab>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
|
<TabPanels>
|
||||||
|
<TabPanel py="2" px="0">
|
||||||
|
<RelayReviews relay={relay} />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel py="2" px="0"></TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RelayView() {
|
||||||
|
const { relay } = useParams<string>();
|
||||||
|
if (!relay) return <>No relay url</>;
|
||||||
|
|
||||||
|
const safeUrl = safeRelayUrl(relay);
|
||||||
|
|
||||||
|
if (!safeUrl) return <>Bad relay url</>;
|
||||||
|
|
||||||
|
return <RelayPage relay={safeUrl} />;
|
||||||
|
}
|
@ -8,7 +8,7 @@ import useSubject from "../../hooks/use-subject";
|
|||||||
import { NostrEvent } from "../../types/nostr-event";
|
import { NostrEvent } from "../../types/nostr-event";
|
||||||
import RelayReviewNote from "../relays/components/relay-review-note";
|
import RelayReviewNote from "../relays/components/relay-review-note";
|
||||||
import { RelayFavicon } from "../../components/relay-favicon";
|
import { RelayFavicon } from "../../components/relay-favicon";
|
||||||
import { DebugButton, RelayJoinAction, RelayMetadata } from "../relays/components/relay-card";
|
import { RelayDebugButton, RelayJoinAction, RelayMetadata } from "../relays/components/relay-card";
|
||||||
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
import IntersectionObserverProvider from "../../providers/intersection-observer";
|
||||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||||
|
|
||||||
@ -21,12 +21,12 @@ function Relay({ url, reviews }: { url: string; reviews: NostrEvent[] }) {
|
|||||||
{url}
|
{url}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<RelayJoinAction url={url} />
|
<RelayJoinAction url={url} size="sm" />
|
||||||
<Button as={RouterLink} to={`/global?relay=${url}`} size="sm">
|
<Button as={RouterLink} to={`/global?relay=${url}`} size="sm">
|
||||||
Notes
|
Notes
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<DebugButton url={url} />
|
<RelayDebugButton url={url} size="sm" />
|
||||||
</Flex>
|
</Flex>
|
||||||
<RelayMetadata url={url} />
|
<RelayMetadata url={url} />
|
||||||
<Flex py="0" direction="column" gap="2">
|
<Flex py="0" direction="column" gap="2">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user