refactor: remove electron-related code
This commit is contained in:
48
src/pages/primary/NoteListPage/index.tsx
Normal file
48
src/pages/primary/NoteListPage/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import NoteList from '@/components/NoteList'
|
||||
import RelaySettings from '@/components/RelaySettings'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export default function NoteListPage() {
|
||||
const layoutRef = useRef<{ scrollToTop: () => void }>(null)
|
||||
const { relayUrls } = useRelaySettings()
|
||||
const relayUrlsString = JSON.stringify(relayUrls)
|
||||
useEffect(() => {
|
||||
if (layoutRef.current) {
|
||||
layoutRef.current.scrollToTop()
|
||||
}
|
||||
}, [relayUrlsString])
|
||||
|
||||
if (!relayUrls.length) {
|
||||
return (
|
||||
<PrimaryPageLayout>
|
||||
<div className="w-full text-center">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button title="relay settings" size="lg">
|
||||
Choose a relay group
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-96 h-[450px] p-0">
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-4">
|
||||
<RelaySettings />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</PrimaryPageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PrimaryPageLayout ref={layoutRef}>
|
||||
<NoteList relayUrls={relayUrls} />
|
||||
</PrimaryPageLayout>
|
||||
)
|
||||
}
|
||||
62
src/pages/secondary/FollowingListPage/index.tsx
Normal file
62
src/pages/secondary/FollowingListPage/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import UserItem from '@/components/UserItem'
|
||||
import { useFetchFollowings, useFetchProfile } from '@/hooks'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function FollowingListPage({ id }: { id?: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { profile } = useFetchProfile(id)
|
||||
const { followings } = useFetchFollowings(profile?.pubkey)
|
||||
const [visibleFollowings, setVisibleFollowings] = useState<string[]>([])
|
||||
const bottomRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleFollowings(followings.slice(0, 10))
|
||||
}, [followings])
|
||||
|
||||
useEffect(() => {
|
||||
const options = {
|
||||
root: null,
|
||||
rootMargin: '10px',
|
||||
threshold: 1
|
||||
}
|
||||
|
||||
const observerInstance = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && followings.length > visibleFollowings.length) {
|
||||
setVisibleFollowings((prev) => [
|
||||
...prev,
|
||||
...followings.slice(prev.length, prev.length + 10)
|
||||
])
|
||||
}
|
||||
}, options)
|
||||
|
||||
const currentBottomRef = bottomRef.current
|
||||
if (currentBottomRef) {
|
||||
observerInstance.observe(currentBottomRef)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (observerInstance && currentBottomRef) {
|
||||
observerInstance.unobserve(currentBottomRef)
|
||||
}
|
||||
}
|
||||
}, [visibleFollowings, followings])
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout
|
||||
titlebarContent={
|
||||
profile?.username
|
||||
? t("username's following", { username: profile.username })
|
||||
: t('following')
|
||||
}
|
||||
>
|
||||
<div className="space-y-2 max-sm:px-4">
|
||||
{visibleFollowings.map((pubkey, index) => (
|
||||
<UserItem key={`${index}-${pubkey}`} pubkey={pubkey} />
|
||||
))}
|
||||
{followings.length > visibleFollowings.length && <div ref={bottomRef} />}
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
13
src/pages/secondary/HomePage/index.tsx
Normal file
13
src/pages/secondary/HomePage/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function HomePage() {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<SecondaryPageLayout hideBackButton hideScrollToTopButton>
|
||||
<div className="text-muted-foreground w-full h-full flex items-center justify-center">
|
||||
{t('Welcome! 🥳')}
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
11
src/pages/secondary/LoadingPage/index.tsx
Normal file
11
src/pages/secondary/LoadingPage/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
|
||||
export default function LoadingPage({ title }: { title?: string }) {
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={title}>
|
||||
<div className="text-muted-foreground text-center">
|
||||
<div>Loading...</div>
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
15
src/pages/secondary/NotFoundPage/index.tsx
Normal file
15
src/pages/secondary/NotFoundPage/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function NotFoundPage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout hideBackButton>
|
||||
<div className="text-muted-foreground w-full h-full flex flex-col items-center justify-center gap-2">
|
||||
<div>{t('Lost in the void')} 🌌</div>
|
||||
<div>(404)</div>
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
54
src/pages/secondary/NoteListPage/index.tsx
Normal file
54
src/pages/secondary/NoteListPage/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import NoteList from '@/components/NoteList'
|
||||
import { useSearchParams } from '@/hooks'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { isWebsocketUrl } from '@/lib/url'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import { Filter } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function NoteListPage() {
|
||||
const { t } = useTranslation()
|
||||
const { relayUrls, searchableRelayUrls } = useRelaySettings()
|
||||
const { searchParams } = useSearchParams()
|
||||
const relayUrlsString = JSON.stringify(relayUrls)
|
||||
const {
|
||||
title = '',
|
||||
filter,
|
||||
urls
|
||||
} = useMemo<{
|
||||
title?: string
|
||||
filter?: Filter
|
||||
urls: string[]
|
||||
}>(() => {
|
||||
const hashtag = searchParams.get('t')
|
||||
if (hashtag) {
|
||||
return { title: `# ${hashtag}`, filter: { '#t': [hashtag] }, urls: relayUrls }
|
||||
}
|
||||
const search = searchParams.get('s')
|
||||
if (search) {
|
||||
return { title: `${t('search')}: ${search}`, filter: { search }, urls: relayUrls }
|
||||
}
|
||||
const relayUrl = searchParams.get('relay')
|
||||
if (relayUrl && isWebsocketUrl(relayUrl)) {
|
||||
return { title: relayUrl, urls: [relayUrl] }
|
||||
}
|
||||
return { urls: relayUrls }
|
||||
}, [searchParams, relayUrlsString])
|
||||
|
||||
if (filter?.search && searchableRelayUrls.length === 0) {
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={title}>
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
{t('The relays you are connected to do not support search')}
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={title}>
|
||||
<NoteList key={title} filter={filter} relayUrls={urls} />
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
65
src/pages/secondary/NotePage/index.tsx
Normal file
65
src/pages/secondary/NotePage/index.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import Note from '@/components/Note'
|
||||
import ReplyNoteList from '@/components/ReplyNoteList'
|
||||
import UserAvatar from '@/components/UserAvatar'
|
||||
import Username from '@/components/Username'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { useFetchEvent } from '@/hooks'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { getParentEventId, getRootEventId } from '@/lib/event'
|
||||
import { toNote } from '@/lib/link'
|
||||
import { useMemo } from 'react'
|
||||
import NotFoundPage from '../NotFoundPage'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function NotePage({ id }: { id?: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { event, isFetching } = useFetchEvent(id)
|
||||
const parentEventId = useMemo(() => getParentEventId(event), [event])
|
||||
const rootEventId = useMemo(() => getRootEventId(event), [event])
|
||||
|
||||
if (!event && isFetching) {
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={t('note')}>
|
||||
<div className="max-sm:px-4">
|
||||
<Skeleton className="w-10 h-10 rounded-full" />
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
if (!event) return <NotFoundPage />
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={t('note')}>
|
||||
<div className="max-sm:px-4">
|
||||
<ParentNote key={`root-note-${event.id}`} eventId={rootEventId} />
|
||||
<ParentNote key={`parent-note-${event.id}`} eventId={parentEventId} />
|
||||
<Note key={`note-${event.id}`} event={event} fetchNoteStats />
|
||||
</div>
|
||||
<Separator className="mb-2 mt-4" />
|
||||
<ReplyNoteList key={`reply-note-list-${event.id}`} event={event} className="max-sm:px-2" />
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
function ParentNote({ eventId }: { eventId?: string }) {
|
||||
const { push } = useSecondaryPage()
|
||||
const { event } = useFetchEvent(eventId)
|
||||
if (!event) return null
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card
|
||||
className="flex space-x-1 p-1 items-center hover:bg-muted/50 cursor-pointer text-sm text-muted-foreground hover:text-foreground"
|
||||
onClick={() => push(toNote(event))}
|
||||
>
|
||||
<UserAvatar userId={event.pubkey} size="tiny" />
|
||||
<Username userId={event.pubkey} className="font-semibold" skeletonClassName="h-4" />
|
||||
<div className="truncate">{event.content}</div>
|
||||
</Card>
|
||||
<div className="ml-5 w-px h-2 bg-border" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
15
src/pages/secondary/NotificationListPage/index.tsx
Normal file
15
src/pages/secondary/NotificationListPage/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import NotificationList from '@/components/NotificationList'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function NotificationListPage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={t('notifications')}>
|
||||
<div className="max-sm:px-4">
|
||||
<NotificationList />
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
90
src/pages/secondary/ProfileListPage/index.tsx
Normal file
90
src/pages/secondary/ProfileListPage/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import UserItem from '@/components/UserItem'
|
||||
import { useSearchParams } from '@/hooks'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import client from '@/services/client.service'
|
||||
import dayjs from 'dayjs'
|
||||
import { Filter } from 'nostr-tools'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const LIMIT = 50
|
||||
|
||||
export default function ProfileListPage() {
|
||||
const { t } = useTranslation()
|
||||
const { searchParams } = useSearchParams()
|
||||
const { relayUrls, searchableRelayUrls } = useRelaySettings()
|
||||
const [until, setUntil] = useState<number>(() => dayjs().unix())
|
||||
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||
const [pubkeySet, setPubkeySet] = useState(new Set<string>())
|
||||
const bottomRef = useRef<HTMLDivElement>(null)
|
||||
const filter = useMemo(() => {
|
||||
const f: Filter = { until }
|
||||
const search = searchParams.get('s')
|
||||
if (search) {
|
||||
f.search = search
|
||||
}
|
||||
return f
|
||||
}, [searchParams, until])
|
||||
const urls = useMemo(() => {
|
||||
return filter.search ? searchableRelayUrls : relayUrls
|
||||
}, [relayUrls, searchableRelayUrls, filter])
|
||||
const title = useMemo(() => {
|
||||
return filter.search ? `${t('search')}: ${filter.search}` : t('all users')
|
||||
}, [filter])
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasMore) return
|
||||
const options = {
|
||||
root: null,
|
||||
rootMargin: '10px',
|
||||
threshold: 1
|
||||
}
|
||||
|
||||
const observerInstance = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && hasMore) {
|
||||
loadMore()
|
||||
}
|
||||
}, options)
|
||||
|
||||
const currentBottomRef = bottomRef.current
|
||||
|
||||
if (currentBottomRef) {
|
||||
observerInstance.observe(currentBottomRef)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (observerInstance && currentBottomRef) {
|
||||
observerInstance.unobserve(currentBottomRef)
|
||||
}
|
||||
}
|
||||
}, [hasMore, filter, urls])
|
||||
|
||||
async function loadMore() {
|
||||
if (urls.length === 0) {
|
||||
return setHasMore(false)
|
||||
}
|
||||
const profiles = await client.fetchProfiles(urls, { ...filter, limit: LIMIT })
|
||||
const newPubkeySet = new Set<string>()
|
||||
profiles.forEach((profile) => {
|
||||
if (!pubkeySet.has(profile.pubkey)) {
|
||||
newPubkeySet.add(profile.pubkey)
|
||||
}
|
||||
})
|
||||
setPubkeySet((prev) => new Set([...prev, ...newPubkeySet]))
|
||||
setHasMore(profiles.length >= LIMIT)
|
||||
const lastProfileCreatedAt = profiles[profiles.length - 1].created_at
|
||||
setUntil(lastProfileCreatedAt ? lastProfileCreatedAt - 1 : 0)
|
||||
}
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={title}>
|
||||
<div className="space-y-2 max-sm:px-4">
|
||||
{Array.from(pubkeySet).map((pubkey, index) => (
|
||||
<UserItem key={`${index}-${pubkey}`} pubkey={pubkey} />
|
||||
))}
|
||||
{hasMore && <div ref={bottomRef} />}
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
27
src/pages/secondary/ProfilePage/PubkeyCopy.tsx
Normal file
27
src/pages/secondary/ProfilePage/PubkeyCopy.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { formatNpub } from '@/lib/pubkey'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
export default function PubkeyCopy({ pubkey }: { pubkey: string }) {
|
||||
const npub = useMemo(() => (pubkey ? nip19.npubEncode(pubkey) : ''), [pubkey])
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const copyNpub = () => {
|
||||
if (!npub) return
|
||||
|
||||
navigator.clipboard.writeText(npub)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex gap-2 text-sm text-muted-foreground items-center bg-muted w-fit px-2 rounded-full hover:text-foreground cursor-pointer"
|
||||
onClick={() => copyNpub()}
|
||||
>
|
||||
<div>{formatNpub(npub, 24)}</div>
|
||||
{copied ? <Check size={14} /> : <Copy size={14} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
23
src/pages/secondary/ProfilePage/QrCodePopover.tsx
Normal file
23
src/pages/secondary/ProfilePage/QrCodePopover.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { QrCode } from 'lucide-react'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
|
||||
export default function QrCodePopover({ pubkey }: { pubkey: string }) {
|
||||
const npub = useMemo(() => (pubkey ? nip19.npubEncode(pubkey) : ''), [pubkey])
|
||||
if (!npub) return null
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<div className="bg-muted rounded-full h-5 w-5 flex flex-col items-center justify-center text-muted-foreground hover:text-foreground">
|
||||
<QrCode size={14} />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-fit h-fit">
|
||||
<QRCodeSVG value={`nostr:${npub}`} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
110
src/pages/secondary/ProfilePage/index.tsx
Normal file
110
src/pages/secondary/ProfilePage/index.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import FollowButton from '@/components/FollowButton'
|
||||
import Nip05 from '@/components/Nip05'
|
||||
import NoteList from '@/components/NoteList'
|
||||
import ProfileAbout from '@/components/ProfileAbout'
|
||||
import ProfileBanner from '@/components/ProfileBanner'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { useFetchFollowings, useFetchProfile } from '@/hooks'
|
||||
import { useFetchRelayList } from '@/hooks/useFetchRelayList'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { toFollowingList } from '@/lib/link'
|
||||
import { generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { SecondaryPageLink } from '@/PageManager'
|
||||
import { useFollowList } from '@/providers/FollowListProvider'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NotFoundPage from '../NotFoundPage'
|
||||
import PubkeyCopy from './PubkeyCopy'
|
||||
import QrCodePopover from './QrCodePopover'
|
||||
|
||||
export default function ProfilePage({ id }: { id?: string }) {
|
||||
const { t } = useTranslation()
|
||||
const { profile, isFetching } = useFetchProfile(id)
|
||||
const relayList = useFetchRelayList(profile?.pubkey)
|
||||
const { relayUrls: currentRelayUrls } = useRelaySettings()
|
||||
const { pubkey: accountPubkey } = useNostr()
|
||||
const { followings: selfFollowings } = useFollowList()
|
||||
const { followings } = useFetchFollowings(profile?.pubkey)
|
||||
const isFollowingYou = useMemo(() => {
|
||||
return (
|
||||
!!accountPubkey && accountPubkey !== profile?.pubkey && followings.includes(accountPubkey)
|
||||
)
|
||||
}, [followings, profile, accountPubkey])
|
||||
const defaultImage = useMemo(
|
||||
() => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''),
|
||||
[profile]
|
||||
)
|
||||
const isSelf = accountPubkey === profile?.pubkey
|
||||
|
||||
if (!profile && isFetching) {
|
||||
return (
|
||||
<SecondaryPageLayout>
|
||||
<div className="max-sm:px-4">
|
||||
<div className="relative bg-cover bg-center w-full aspect-[21/9] rounded-lg mb-2">
|
||||
<Skeleton className="w-full h-full object-cover rounded-lg" />
|
||||
<Skeleton className="w-24 h-24 absolute bottom-0 left-4 translate-y-1/2 border-4 border-background rounded-full" />
|
||||
</div>
|
||||
<Skeleton className="h-5 w-28 mt-14 mb-1" />
|
||||
<Skeleton className="h-5 w-56 mt-2 my-1 rounded-full" />
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
if (!profile) return <NotFoundPage />
|
||||
|
||||
const { banner, username, nip05, about, avatar, pubkey } = profile
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={username}>
|
||||
<div className="max-sm:px-4">
|
||||
<div className="relative bg-cover bg-center w-full aspect-[21/9] rounded-lg mb-2">
|
||||
<ProfileBanner
|
||||
banner={banner}
|
||||
pubkey={pubkey}
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
/>
|
||||
<Avatar className="w-24 h-24 absolute bottom-0 left-4 translate-y-1/2 border-4 border-background">
|
||||
<AvatarImage src={avatar} className="object-cover object-center" />
|
||||
<AvatarFallback>
|
||||
<img src={defaultImage} />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="flex justify-end h-8 gap-2 items-center">
|
||||
{isFollowingYou && (
|
||||
<div className="text-muted-foreground rounded-full bg-muted text-xs h-fit px-2">
|
||||
{t('Follows you')}
|
||||
</div>
|
||||
)}
|
||||
<FollowButton pubkey={pubkey} />
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<div className="text-xl font-semibold">{username}</div>
|
||||
{nip05 && <Nip05 nip05={nip05} pubkey={pubkey} />}
|
||||
<div className="flex gap-1 mt-1">
|
||||
<PubkeyCopy pubkey={pubkey} />
|
||||
<QrCodePopover pubkey={pubkey} />
|
||||
</div>
|
||||
<ProfileAbout about={about} className="text-wrap break-words whitespace-pre-wrap mt-2" />
|
||||
<SecondaryPageLink
|
||||
to={toFollowingList(pubkey)}
|
||||
className="mt-2 flex gap-1 hover:underline text-sm w-fit"
|
||||
>
|
||||
{isSelf ? selfFollowings.length : followings.length}
|
||||
<div className="text-muted-foreground">{t('Following')}</div>
|
||||
</SecondaryPageLink>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="hidden sm:block mt-4 sm:my-4" />
|
||||
<NoteList
|
||||
key={pubkey}
|
||||
filter={{ authors: [pubkey] }}
|
||||
relayUrls={relayList.write.slice(0, 5).concat(currentRelayUrls)}
|
||||
className="max-sm:mt-2"
|
||||
/>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
15
src/pages/secondary/RelaySettingsPage/index.tsx
Normal file
15
src/pages/secondary/RelaySettingsPage/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import RelaySettings from '@/components/RelaySettings'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function RelaySettingsPage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout titlebarContent={t('Relay settings')}>
|
||||
<div className="max-sm:px-4">
|
||||
<RelaySettings hideTitle />
|
||||
</div>
|
||||
</SecondaryPageLayout>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user