9 changed files with 163 additions and 45 deletions
@ -0,0 +1,95 @@ |
|||||||
|
import { Badge } from '@/components/ui/badge' |
||||||
|
import { useFetchRelayInfos } from '@/hooks' |
||||||
|
import { TRelayInfo } from '@/types' |
||||||
|
import { GitBranch, Mail, SquareCode } from 'lucide-react' |
||||||
|
import RelayIcon from '../RelayIcon' |
||||||
|
import UserAvatar from '../UserAvatar' |
||||||
|
import Username from '../Username' |
||||||
|
|
||||||
|
export default function RelayInfo({ url }: { url: string }) { |
||||||
|
const { |
||||||
|
relayInfos: [relayInfo], |
||||||
|
isFetching |
||||||
|
} = useFetchRelayInfos([url]) |
||||||
|
if (isFetching || !relayInfo) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="px-4 space-y-4 mb-2"> |
||||||
|
<div className="space-y-2"> |
||||||
|
<div className="flex gap-2 items-center"> |
||||||
|
<RelayIcon url={url} /> |
||||||
|
{relayInfo.name && <div className="text-xl font-semibold">{relayInfo.name}</div>} |
||||||
|
</div> |
||||||
|
<RelayBadges relayInfo={relayInfo} /> |
||||||
|
{!!relayInfo.tags?.length && |
||||||
|
relayInfo.tags.map((tag) => <Badge variant="secondary">{tag}</Badge>)} |
||||||
|
{relayInfo.description && ( |
||||||
|
<div className="text-wrap break-words whitespace-pre-wrap mt-2"> |
||||||
|
{relayInfo.description} |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
<div className="flex flex-wrap gap-4"> |
||||||
|
{relayInfo.pubkey && ( |
||||||
|
<div className="space-y-2 flex-1"> |
||||||
|
<div className="text-sm font-semibold text-muted-foreground">Operator</div> |
||||||
|
<div className="flex gap-2 items-center"> |
||||||
|
<UserAvatar userId={relayInfo.pubkey} size="small" /> |
||||||
|
<Username userId={relayInfo.pubkey} className="font-semibold whitespace-nowrap" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
{relayInfo.contact && ( |
||||||
|
<div className="space-y-2 flex-1"> |
||||||
|
<div className="text-sm font-semibold text-muted-foreground">Contact</div> |
||||||
|
<div className="flex gap-2 items-center font-semibold whitespace-nowrap"> |
||||||
|
<Mail /> |
||||||
|
{relayInfo.contact} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
{relayInfo.software && ( |
||||||
|
<div className="space-y-2 flex-1"> |
||||||
|
<div className="text-sm font-semibold text-muted-foreground">Software</div> |
||||||
|
<div className="flex gap-2 items-center font-semibold whitespace-nowrap"> |
||||||
|
<SquareCode /> |
||||||
|
{formatSoftware(relayInfo.software)} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
{relayInfo.version && ( |
||||||
|
<div className="space-y-2 flex-1"> |
||||||
|
<div className="text-sm font-semibold text-muted-foreground">Version</div> |
||||||
|
<div className="flex gap-2 items-center font-semibold whitespace-nowrap"> |
||||||
|
<GitBranch /> |
||||||
|
{relayInfo.version} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
function formatSoftware(software: string) { |
||||||
|
const parts = software.split('/') |
||||||
|
return parts[parts.length - 1] |
||||||
|
} |
||||||
|
|
||||||
|
function RelayBadges({ relayInfo }: { relayInfo: TRelayInfo }) { |
||||||
|
return ( |
||||||
|
<div className="flex gap-2"> |
||||||
|
{relayInfo.supported_nips?.includes(42) && ( |
||||||
|
<Badge className="bg-green-400 hover:bg-green-400/80">Auth</Badge> |
||||||
|
)} |
||||||
|
{relayInfo.supported_nips?.includes(50) && ( |
||||||
|
<Badge className="bg-pink-400 hover:bg-pink-400/80">Search</Badge> |
||||||
|
)} |
||||||
|
{relayInfo.limitation?.payment_required && ( |
||||||
|
<Badge className="bg-orange-400 hover:bg-orange-400/80">Payment</Badge> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
import NoteList from '@/components/NoteList' |
||||||
|
import RelayInfo from '@/components/RelayInfo' |
||||||
|
import SaveRelayDropdownMenu from '@/components/SaveRelayDropdownMenu' |
||||||
|
import { Button } from '@/components/ui/button' |
||||||
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' |
||||||
|
import { normalizeUrl, simplifyUrl } from '@/lib/url' |
||||||
|
import { ListPlus } from 'lucide-react' |
||||||
|
import { useMemo } from 'react' |
||||||
|
import NotFoundPage from '../NotFoundPage' |
||||||
|
|
||||||
|
export default function RelayPage({ url, index }: { url?: string; index?: number }) { |
||||||
|
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url]) |
||||||
|
const title = useMemo(() => (url ? simplifyUrl(url) : undefined), [url]) |
||||||
|
|
||||||
|
if (!normalizedUrl) { |
||||||
|
return <NotFoundPage /> |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<SecondaryPageLayout |
||||||
|
index={index} |
||||||
|
title={title} |
||||||
|
controls={ |
||||||
|
<SaveRelayDropdownMenu urls={[normalizedUrl]} asChild> |
||||||
|
<Button variant="ghost" size="titlebar-icon"> |
||||||
|
<ListPlus /> |
||||||
|
</Button> |
||||||
|
</SaveRelayDropdownMenu> |
||||||
|
} |
||||||
|
displayScrollToTopButton |
||||||
|
> |
||||||
|
<RelayInfo url={normalizedUrl} /> |
||||||
|
<NoteList relayUrls={[normalizedUrl]} /> |
||||||
|
</SecondaryPageLayout> |
||||||
|
) |
||||||
|
} |
||||||
Loading…
Reference in new issue