index.tsx raw
1 import { usePrimaryPage } from '@/PageManager'
2 import RelayInfo from '@/components/RelayInfo'
3 import SmeshLoader from '@/components/SmeshLoader'
4 import { Button } from '@/components/ui/button'
5 import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
6 import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
7 import { useFeed } from '@/providers/FeedProvider'
8 import { useNostr } from '@/providers/NostrProvider'
9 import { TPageRef } from '@/types'
10 import { Compass, Info, LogIn } from 'lucide-react'
11 import {
12 Dispatch,
13 forwardRef,
14 SetStateAction,
15 useEffect,
16 useImperativeHandle,
17 useRef,
18 useState
19 } from 'react'
20 import { useTranslation } from 'react-i18next'
21 import FeedButton from './FeedButton'
22 import FollowingFeed from './FollowingFeed'
23 import PinnedFeed from './PinnedFeed'
24 import RelaysFeed from './RelaysFeed'
25
26 const NoteListPage = forwardRef<TPageRef>((_, ref) => {
27 const { addRelayUrls, removeRelayUrls } = useCurrentRelays()
28 const layoutRef = useRef<TPageRef>(null)
29 const { pubkey } = useNostr()
30 const { feedInfo, relayUrls, isReady, switchFeed } = useFeed()
31 const [showRelayDetails, setShowRelayDetails] = useState(false)
32
33 useImperativeHandle(ref, () => layoutRef.current as TPageRef)
34
35 useEffect(() => {
36 if (layoutRef.current) {
37 layoutRef.current.scrollToTop('instant')
38 }
39 }, [JSON.stringify(relayUrls), feedInfo])
40
41 useEffect(() => {
42 if (relayUrls.length) {
43 addRelayUrls(relayUrls)
44 return () => {
45 removeRelayUrls(relayUrls)
46 }
47 }
48 }, [relayUrls])
49
50 let content: React.ReactNode = null
51 if (!isReady) {
52 content = (
53 <div className="flex items-center justify-center pt-16">
54 <SmeshLoader className="w-48 h-48" />
55 </div>
56 )
57 } else if (!feedInfo) {
58 content = <WelcomeGuide />
59 } else if (feedInfo.feedType === 'following' && !pubkey) {
60 switchFeed(null)
61 return null
62 } else if (feedInfo.feedType === 'pinned' && !pubkey) {
63 switchFeed(null)
64 return null
65 } else if (feedInfo.feedType === 'following') {
66 content = <FollowingFeed />
67 } else if (feedInfo.feedType === 'pinned') {
68 content = <PinnedFeed />
69 } else {
70 content = (
71 <>
72 {showRelayDetails && feedInfo.feedType === 'relay' && !!feedInfo.id && (
73 <RelayInfo url={feedInfo.id!} className="mb-2 pt-3" />
74 )}
75 <RelaysFeed />
76 </>
77 )
78 }
79
80 return (
81 <PrimaryPageLayout
82 pageName="home"
83 ref={layoutRef}
84 titlebar={
85 <NoteListPageTitlebar
86 layoutRef={layoutRef}
87 showRelayDetails={showRelayDetails}
88 setShowRelayDetails={
89 feedInfo?.feedType === 'relay' && !!feedInfo.id ? setShowRelayDetails : undefined
90 }
91 />
92 }
93 displayScrollToTopButton
94 >
95 {content}
96 </PrimaryPageLayout>
97 )
98 })
99 NoteListPage.displayName = 'NoteListPage'
100 export default NoteListPage
101
102 function NoteListPageTitlebar({
103 layoutRef,
104 showRelayDetails,
105 setShowRelayDetails
106 }: {
107 layoutRef?: React.RefObject<TPageRef>
108 showRelayDetails?: boolean
109 setShowRelayDetails?: Dispatch<SetStateAction<boolean>>
110 }) {
111 return (
112 <div className="flex gap-1 items-center h-full justify-between">
113 <FeedButton className="flex-1 max-w-fit w-0" />
114 <div className="shrink-0 flex gap-1 items-center">
115 {setShowRelayDetails && (
116 <Button
117 variant="ghost"
118 size="titlebar-icon"
119 onClick={(e) => {
120 e.stopPropagation()
121 setShowRelayDetails((show) => !show)
122
123 if (!showRelayDetails) {
124 layoutRef?.current?.scrollToTop('smooth')
125 }
126 }}
127 className={showRelayDetails ? 'bg-accent/50' : ''}
128 >
129 <Info />
130 </Button>
131 )}
132 </div>
133 </div>
134 )
135 }
136
137 function WelcomeGuide() {
138 const { t } = useTranslation()
139 const { navigate } = usePrimaryPage()
140 const { checkLogin } = useNostr()
141
142 return (
143 <div className="flex flex-col items-center justify-center min-h-[60vh] px-4 text-center space-y-6">
144 <SmeshLoader className="w-40 h-40" />
145 <div className="space-y-2">
146 <h2 className="text-2xl font-bold">{t('Welcome to Smesh')}</h2>
147 <p className="text-muted-foreground max-w-md">
148 {t(
149 'Smesh is a client focused on browsing relays. Get started by exploring interesting relays or login to view your following feed.'
150 )}
151 </p>
152 </div>
153
154 <div className="flex flex-col sm:flex-row gap-3 w-full max-w-md">
155 <Button size="lg" className="w-full" onClick={() => navigate('explore')}>
156 <Compass className="size-5" />
157 {t('Explore')}
158 </Button>
159
160 <Button size="lg" className="w-full" variant="outline" onClick={() => checkLogin()}>
161 <LogIn className="size-5" />
162 {t('Login')}
163 </Button>
164 </div>
165 </div>
166 )
167 }
168