import { useState, useEffect, useRef, Fragment, useCallback } from 'react' import { Route, Link, Routes, useSearchParams } from 'react-router-dom' import svg4everybody from 'svg4everybody' import Icon from './ui/Icon' import { Discover, Discussions, Blog, Tag, Home } from './ui/Feeds' import { Friends, Readers } from './ui/Users' import Settings from './ui/Settings' import Contacts from './ui/Contacts' import Chat from './ui/Chat' import Header from './ui/Header' import Post from './ui/Post' import Thread from './ui/Thread' import Login from './ui/Login' import { useCookies } from 'react-cookie' import { me, trends } from './api' import { useVisitor } from './ui/VisitorContext' import Avatar from './ui/Avatar' import { Toaster } from 'react-hot-toast' /** * * @param {import('react').PropsWithChildren<{}> & { * footer: string * }} props props */ export default function App({ footer }) { let contentRef = useRef(null) const [cookie, setCookie] = useCookies(['hash']) const [allTrends, setAllTrends] = useState([]) const [visitor, setVisitor] = useVisitor() const params = useSearchParams() useEffect(() => { svg4everybody() if (params['hash']) { setCookie('hash', params['hash'], { path: '/' }) let retpath = params['retpath'] || `${window.location.protocol}//${window.location.host}${window.location.pathname}` window.history.replaceState({}, document.title, retpath) } }, [setCookie, footer, params]) let updateStatus = useCallback(() => { // refresh server visitor state (unread counters) me().then(visitor => { setVisitor(visitor) }).catch(console.error) }, [setVisitor]) const [hash, setHash] = useState(cookie.hash) const [eventSource, setEventSource] = /** @param EventSource? */ useState({}) /** * @param {import("./api").SecureUser} visitor */ let auth = useCallback((visitor) => { setVisitor(prevState => { if (visitor.hash != prevState.hash) { setHash(visitor.hash) } return visitor }) }, [setVisitor]) useEffect(() => { let es const anonymousUser = { uid: 0 } if (hash) { me().then(visitor => auth(visitor)) .catch(() => setVisitor(anonymousUser)) if ('EventSource' in window) { const eventParams = new URLSearchParams({ hash: hash }) let url = new URL(`https://juick.com/api/events?${eventParams.toString()}`) console.log(url.toString()) es = new EventSource(url.toString()) es.onopen = () => { console.log('online') } es.onerror = () => { es.removeEventListener('read', updateStatus) es.removeEventListener('msg', updateStatus) } es.addEventListener('read', updateStatus) es.addEventListener('msg', updateStatus) setEventSource(es) } } else { setVisitor(anonymousUser) } return (() => { if (es && es.removeEventListener) { es.removeEventListener('read', updateStatus) es.removeEventListener('msg', updateStatus) } }) }, [auth, hash, setVisitor, updateStatus]) useEffect(() => { const getTrends = async () => { setAllTrends(await trends()) } getTrends().catch(console.error) }, []) return ( <> <Header /> <Toaster /> { <aside id="sidebar"> <div id="sidebar_wrapper"> { <nav id="global"> {visitor.uid > 0 ? <> <div id="ctitle"> <Avatar user={visitor} /> </div> <Link to={{ pathname: '/post' }}> <Icon name="ei-pencil" size="s" /> <span className="desktop">Post</span> </Link> <Link to="/?show=my"> <Icon name="ei-clock" size="s" /> <span className="desktop">My feed</span> </Link> <Link to="/pm"> <Icon name="ei-envelope" size="s" /> <span className="desktop">Messages</span> </Link> <Link to="/settings" rel="nofollow"> <Icon name="ei-gear" size="s" /> <span className="desktop">Settings</span> </Link> </> : <Link to={{ pathname: '/login' }}> <Icon name="ei-user" size="s" /> <span className="desktop">Login</span> </Link> } <Link to={{ pathname: '/discover' }} rel="nofollow"> <Icon name="ei-search" size="s" /> <span className="desktop">Discover</span> </Link> <Link to="/?show=discuss"> <Icon name="ei-bell" size="s" /> <span className="desktop">Discussions</span> </Link> <Link to='/?media=1' rel="nofollow"> <Icon name="ei-camera" size="s" /> <span className="desktop">Photos</span> </Link> </nav> } <div className="tags desktop"> <h4>Trends</h4> {allTrends.map((it, index) => ( <Fragment key={it.tag}> {index > 0 && ' '} <Link to={`/tag/${it.tag}`}>#{it.tag}</Link> </Fragment> ))} </div> <div id="footer" className="desktop"> <div id="footer-left">© 2008-2023, Juick team {footer && (<><br />Sponsors: <span dangerouslySetInnerHTML={{ __html: footer }}></span></>)}</div> <div id="footer-right"> · <Link to="/help/contacts" rel="nofollow">Contacts</Link> · <Link to="/help/tos" rel="nofollow">TOS</Link> </div> </div> </div> </aside> } <section id="content" ref={contentRef}> <Routes> <Route exact path="/" element={<Discussions />} /> <Route exact path="/home" element={<Home />} /> <Route exact path="/discover" element={<Discover />} /> <Route exact path="/settings" element={<Settings onChange={auth} />} /> <Route exact path="/login" element={<Login onAuth={auth} />} /> <Route exact path="/post" element={<Post />} /> <Route exact path="/pm" element={<Contacts />} /> <Route exact path="/pm/:user" element={<Chat connection={eventSource} />} /> <Route exact path="/:user/friends" element={<Friends />} /> <Route exact path="/:user/readers" element={<Readers />} /> <Route exact path="/:user" element={<Blog />} /> <Route exact path="/tag/:tag" element={<Tag />} /> <Route exact path="/:user/:mid" element={<Thread connection={eventSource} />} /> </Routes> </section> </> ) }