213 lines
5.9 KiB
TypeScript
213 lines
5.9 KiB
TypeScript
import { usePrimaryPage, useSecondaryPage } from '@/PageManager'
|
|
import PostEditor from '@/components/PostEditor'
|
|
import RelayInfo from '@/components/RelayInfo'
|
|
import { Button } from '@/components/ui/button'
|
|
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
|
import { toSearch } from '@/lib/link'
|
|
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
|
|
import { useFeed } from '@/providers/FeedProvider'
|
|
import { useNostr } from '@/providers/NostrProvider'
|
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
|
import { TPageRef } from '@/types'
|
|
import { Compass, Info, LogIn, PencilLine, Search, Sparkles } from 'lucide-react'
|
|
import {
|
|
Dispatch,
|
|
forwardRef,
|
|
SetStateAction,
|
|
useEffect,
|
|
useImperativeHandle,
|
|
useRef,
|
|
useState
|
|
} from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import FeedButton from './FeedButton'
|
|
import FollowingFeed from './FollowingFeed'
|
|
import PinnedFeed from './PinnedFeed'
|
|
import RelaysFeed from './RelaysFeed'
|
|
|
|
const NoteListPage = forwardRef<TPageRef>((_, ref) => {
|
|
const { t } = useTranslation()
|
|
const { addRelayUrls, removeRelayUrls } = useCurrentRelays()
|
|
const layoutRef = useRef<TPageRef>(null)
|
|
const { pubkey } = useNostr()
|
|
const { feedInfo, relayUrls, isReady, switchFeed } = useFeed()
|
|
const [showRelayDetails, setShowRelayDetails] = useState(false)
|
|
|
|
useImperativeHandle(ref, () => layoutRef.current as TPageRef)
|
|
|
|
useEffect(() => {
|
|
if (layoutRef.current) {
|
|
layoutRef.current.scrollToTop('instant')
|
|
}
|
|
}, [JSON.stringify(relayUrls), feedInfo])
|
|
|
|
useEffect(() => {
|
|
if (relayUrls.length) {
|
|
addRelayUrls(relayUrls)
|
|
return () => {
|
|
removeRelayUrls(relayUrls)
|
|
}
|
|
}
|
|
}, [relayUrls])
|
|
|
|
let content: React.ReactNode = null
|
|
if (!isReady) {
|
|
content = (
|
|
<div className="text-center text-sm text-muted-foreground pt-3">{t('loading...')}</div>
|
|
)
|
|
} else if (!feedInfo) {
|
|
content = <WelcomeGuide />
|
|
} else if (feedInfo.feedType === 'following' && !pubkey) {
|
|
switchFeed(null)
|
|
return null
|
|
} else if (feedInfo.feedType === 'pinned' && !pubkey) {
|
|
switchFeed(null)
|
|
return null
|
|
} else if (feedInfo.feedType === 'following') {
|
|
content = <FollowingFeed />
|
|
} else if (feedInfo.feedType === 'pinned') {
|
|
content = <PinnedFeed />
|
|
} else {
|
|
content = (
|
|
<>
|
|
{showRelayDetails && feedInfo.feedType === 'relay' && !!feedInfo.id && (
|
|
<RelayInfo url={feedInfo.id!} className="mb-2 pt-3" />
|
|
)}
|
|
<RelaysFeed />
|
|
</>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<PrimaryPageLayout
|
|
pageName="home"
|
|
ref={layoutRef}
|
|
titlebar={
|
|
<NoteListPageTitlebar
|
|
layoutRef={layoutRef}
|
|
showRelayDetails={showRelayDetails}
|
|
setShowRelayDetails={
|
|
feedInfo?.feedType === 'relay' && !!feedInfo.id ? setShowRelayDetails : undefined
|
|
}
|
|
/>
|
|
}
|
|
displayScrollToTopButton
|
|
>
|
|
{content}
|
|
</PrimaryPageLayout>
|
|
)
|
|
})
|
|
NoteListPage.displayName = 'NoteListPage'
|
|
export default NoteListPage
|
|
|
|
function NoteListPageTitlebar({
|
|
layoutRef,
|
|
showRelayDetails,
|
|
setShowRelayDetails
|
|
}: {
|
|
layoutRef?: React.RefObject<TPageRef>
|
|
showRelayDetails?: boolean
|
|
setShowRelayDetails?: Dispatch<SetStateAction<boolean>>
|
|
}) {
|
|
const { isSmallScreen } = useScreenSize()
|
|
|
|
return (
|
|
<div className="flex gap-1 items-center h-full justify-between">
|
|
<FeedButton className="flex-1 max-w-fit w-0" />
|
|
<div className="shrink-0 flex gap-1 items-center">
|
|
{setShowRelayDetails && (
|
|
<Button
|
|
variant="ghost"
|
|
size="titlebar-icon"
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
setShowRelayDetails((show) => !show)
|
|
|
|
if (!showRelayDetails) {
|
|
layoutRef?.current?.scrollToTop('smooth')
|
|
}
|
|
}}
|
|
className={showRelayDetails ? 'bg-accent/50' : ''}
|
|
>
|
|
<Info />
|
|
</Button>
|
|
)}
|
|
{isSmallScreen && (
|
|
<>
|
|
<SearchButton />
|
|
<PostButton />
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function PostButton() {
|
|
const { checkLogin } = useNostr()
|
|
const [open, setOpen] = useState(false)
|
|
|
|
return (
|
|
<>
|
|
<Button
|
|
variant="ghost"
|
|
size="titlebar-icon"
|
|
onClick={(e) => {
|
|
e.stopPropagation()
|
|
checkLogin(() => {
|
|
setOpen(true)
|
|
})
|
|
}}
|
|
>
|
|
<PencilLine />
|
|
</Button>
|
|
<PostEditor open={open} setOpen={setOpen} />
|
|
</>
|
|
)
|
|
}
|
|
|
|
function SearchButton() {
|
|
const { push } = useSecondaryPage()
|
|
|
|
return (
|
|
<Button variant="ghost" size="titlebar-icon" onClick={() => push(toSearch())}>
|
|
<Search />
|
|
</Button>
|
|
)
|
|
}
|
|
|
|
function WelcomeGuide() {
|
|
const { t } = useTranslation()
|
|
const { navigate } = usePrimaryPage()
|
|
const { checkLogin } = useNostr()
|
|
|
|
return (
|
|
<div className="flex flex-col items-center justify-center min-h-[60vh] px-4 text-center space-y-6">
|
|
<div className="space-y-2">
|
|
<div className="flex items-center w-full justify-center gap-2">
|
|
<Sparkles className="text-yellow-400" />
|
|
<h2 className="text-2xl font-bold">{t('Welcome to Jumble')}</h2>
|
|
<Sparkles className="text-yellow-400" />
|
|
</div>
|
|
<p className="text-muted-foreground max-w-md">
|
|
{t(
|
|
'Jumble is a client focused on browsing relays. Get started by exploring interesting relays or login to view your following feed.'
|
|
)}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col sm:flex-row gap-3 w-full max-w-md">
|
|
<Button size="lg" className="w-full" onClick={() => navigate('explore')}>
|
|
<Compass className="size-5" />
|
|
{t('Explore')}
|
|
</Button>
|
|
|
|
<Button size="lg" className="w-full" variant="outline" onClick={() => checkLogin()}>
|
|
<LogIn className="size-5" />
|
|
{t('Login')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|