diff options
author | Vitaly Takmazov | 2022-10-31 22:48:30 +0300 |
---|---|---|
committer | Vitaly Takmazov | 2023-01-13 10:37:58 +0300 |
commit | 8887e1b51565b992f34c955c459125eb85b28483 (patch) | |
tree | 7fc8130f523014864e2d60aa9628e7a7ee7e7dd5 /vnext | |
parent | fc96a9a206a825171da87a7f23cc2ea16b1d645d (diff) |
`useVisitor` hook
Diffstat (limited to 'vnext')
-rw-r--r-- | vnext/server/middleware/renderer.js | 9 | ||||
-rw-r--r-- | vnext/src/App.js | 36 | ||||
-rw-r--r-- | vnext/src/index.js | 13 | ||||
-rw-r--r-- | vnext/src/ui/Chat.js | 7 | ||||
-rw-r--r-- | vnext/src/ui/Comment.js | 5 | ||||
-rw-r--r-- | vnext/src/ui/Contacts.js | 3 | ||||
-rw-r--r-- | vnext/src/ui/Feeds.js | 31 | ||||
-rw-r--r-- | vnext/src/ui/Header.js | 6 | ||||
-rw-r--r-- | vnext/src/ui/Login.js | 12 | ||||
-rw-r--r-- | vnext/src/ui/Message.js | 5 | ||||
-rw-r--r-- | vnext/src/ui/PM.js | 7 | ||||
-rw-r--r-- | vnext/src/ui/Post.js | 6 | ||||
-rw-r--r-- | vnext/src/ui/Settings.js | 14 | ||||
-rw-r--r-- | vnext/src/ui/Thread.js | 9 | ||||
-rw-r--r-- | vnext/src/ui/Users.js | 1 | ||||
-rw-r--r-- | vnext/src/ui/VisitorContext.js | 32 |
16 files changed, 123 insertions, 73 deletions
diff --git a/vnext/server/middleware/renderer.js b/vnext/server/middleware/renderer.js index a4b745f8..ca2ac2df 100644 --- a/vnext/server/middleware/renderer.js +++ b/vnext/server/middleware/renderer.js @@ -7,6 +7,7 @@ import App from '../../src/App'; import { getLinks } from '../sape'; import { StaticRouter } from 'react-router-dom/server'; +import { VisitorProvider } from '../../src/ui/VisitorContext'; const path = require('path'); const fs = require('fs'); @@ -49,9 +50,11 @@ const serverRenderer = async (req, res) => { const propsData = `<script>window.__PROPS__="${toBinary(JSON.stringify(props))}";</script>${marker}`; let didError = false; const { pipe } = ReactDOMServer.renderToPipeableStream( - <StaticRouter location={req.baseUrl} context={routerContext}> - <App {...props} /> - </StaticRouter> + <VisitorProvider> + <StaticRouter location={req.baseUrl} context={routerContext}> + <App {...props} /> + </StaticRouter> + </VisitorProvider> , { onShellReady() { res.statusCode = didError ? 500 : 200; diff --git a/vnext/src/App.js b/vnext/src/App.js index 4e235c46..15e29017 100644 --- a/vnext/src/App.js +++ b/vnext/src/App.js @@ -18,6 +18,7 @@ import Login from './ui/Login'; import { useCookies } from 'react-cookie'; import { me, trends } from './api'; +import { useVisitor } from './ui/VisitorContext'; /** * @@ -32,6 +33,8 @@ export default function App({ footer }) { const [allTrends, setAllTrends] = useState([]); + const [visitor, setVisitor] = useVisitor(); + useEffect(() => { svg4everybody(); let params = qs.parse(window.location.search.substring(1)); @@ -42,15 +45,6 @@ export default function App({ footer }) { } }, [setCookie, footer]); - /** - * @type {import('./api').SecureUser} - */ - const unknownUser = { - uid: -1 - }; - - const [visitor, setVisitor] = useState(unknownUser); - let updateStatus = () => { // refresh server visitor state (unread counters) me().then(visitor => { @@ -116,7 +110,7 @@ export default function App({ footer }) { }; return ( <> - <Header visitor={visitor} /> + <Header /> <div id="content_wrapper"> { <aside id="sidebar"> @@ -161,19 +155,19 @@ export default function App({ footer }) { } <section id="content" ref={contentRef}> <Routes> - <Route exact path="/" element={<Discussions visitor={visitor} />} /> - <Route exact path="/home" element={<Home visitor={visitor} />} /> - <Route exact path="/discover" element={<Discover visitor={visitor} />} /> - <Route exact path="/settings" element={<Settings visitor={visitor} onChange={auth} />} /> - <Route exact path="/login" element={<Login visitor={visitor} onAuth={auth} />} /> - <Route exact path="/post" element={<Post visitor={visitor} />} /> - <Route exact path="/pm" element={<Contacts visitor={visitor} />} /> - <Route exact path="/pm/:user" element={<Chat connection={eventSource} visitor={visitor} />} /> + <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 visitor={visitor} />} /> - <Route exact path="/tag/:tag" element={<Tag visitor={visitor} />} /> - <Route exact path="/:user/:mid" element={<Thread connection={eventSource} visitor={visitor} />} /> + <Route exact path="/:user" element={<Blog />} /> + <Route exact path="/tag/:tag" element={<Tag />} /> + <Route exact path="/:user/:mid" element={<Thread connection={eventSource} />} /> </Routes> </section> </div> diff --git a/vnext/src/index.js b/vnext/src/index.js index 5f3c262e..3c354826 100644 --- a/vnext/src/index.js +++ b/vnext/src/index.js @@ -4,6 +4,7 @@ import { BrowserRouter } from 'react-router-dom'; import { CookiesProvider } from 'react-cookie'; import './index.css'; +import { VisitorProvider } from './ui/VisitorContext'; const Juick = lazy(() => import('./App')); @@ -20,11 +21,13 @@ const props = window.__PROPS__ ? JSON.parse(fromBinary(window.__PROPS__)) : {}; const JuickApp = () => ( <StrictMode> - <CookiesProvider> - <BrowserRouter> - <Juick {...props} /> - </BrowserRouter> - </CookiesProvider> + <VisitorProvider> + <CookiesProvider> + <BrowserRouter> + <Juick {...props} /> + </BrowserRouter> + </CookiesProvider> + </VisitorProvider> </StrictMode> ); diff --git a/vnext/src/ui/Chat.js b/vnext/src/ui/Chat.js index 5ecb9c0f..cdf3de1b 100644 --- a/vnext/src/ui/Chat.js +++ b/vnext/src/ui/Chat.js @@ -9,10 +9,10 @@ import UserInfo from './UserInfo'; import { getChat, pm } from '../api'; import './Chat.css'; +import { useVisitor } from './VisitorContext'; /** * @typedef {Object} ChatProps - * @property {import('../api').SecureUser} visitor * @property {EventSource} connection */ @@ -21,11 +21,12 @@ import './Chat.css'; * @param {ChatProps} props */ export default function Chat(props) { + const [visitor] = useVisitor(); const [chats, setChats] = useState([]); const params = useParams(); let loadChat = useCallback((uname) => { - const { hash } = props.visitor; + const { hash } = visitor; setChats([]); if (hash && uname) { getChat(uname) @@ -33,7 +34,7 @@ export default function Chat(props) { setChats(response.data); }); } - }, [props.visitor]); + }, []); let onMessage = useCallback((json) => { const msg = JSON.parse(json.data); diff --git a/vnext/src/ui/Comment.js b/vnext/src/ui/Comment.js index 756b3487..45c80187 100644 --- a/vnext/src/ui/Comment.js +++ b/vnext/src/ui/Comment.js @@ -9,6 +9,7 @@ import MessageInput from './MessageInput'; import { fetchUserUri } from '../api'; import { chatItemStyle } from './helpers/BubbleStyle'; import { format, embedUrls } from '../utils/embed'; +import { useVisitor } from './VisitorContext'; let isMounted; @@ -16,16 +17,16 @@ let isMounted; * @param {{ msg: import('../api').Message, draft: string, - visitor: import('../api').User, active: number, setActive: function, onStartEditing: function, postComment: function }} props */ -export default function Comment({ msg, draft, visitor, active, setActive, onStartEditing, postComment }) { +export default function Comment({ msg, draft, active, setActive, onStartEditing, postComment }) { const embedRef = useRef(); const msgRef = useRef(); + const [visitor] = useVisitor(); const [author, setAuthor] = useState(msg.user); useEffect(() => { if (msgRef.current) { diff --git a/vnext/src/ui/Contacts.js b/vnext/src/ui/Contacts.js index 2a727b82..0005da9a 100644 --- a/vnext/src/ui/Contacts.js +++ b/vnext/src/ui/Contacts.js @@ -6,6 +6,9 @@ import { getChats } from '../api'; import Contact from './Contact.js'; import { ChatSpinner } from './Spinner'; +/** + * + */ export default function Contacts(props) { const [pms, setPms] = useState([]); useEffect(() => { diff --git a/vnext/src/ui/Feeds.js b/vnext/src/ui/Feeds.js index 84319c28..75f1800d 100644 --- a/vnext/src/ui/Feeds.js +++ b/vnext/src/ui/Feeds.js @@ -10,6 +10,7 @@ import Spinner from './Spinner'; import UserInfo from './UserInfo'; import { getMessages } from '../api'; +import { useVisitor } from './VisitorContext'; /** * @typedef {object} Query @@ -21,12 +22,12 @@ import { getMessages } from '../api'; /** * @typedef {object} PageProps * @property {string=} search - * @property {import('../api').SecureUser} visitor * @property {import('../api').Message[]=} msgs */ -function RequireAuth({ visitor, children }) { +function RequireAuth({ children }) { let location = useLocation(); + let [ visitor ] = useVisitor(); if (!visitor.hash) { // Redirect them to the /login page, but save the current location they were // trying to go to when they were redirected. This allows us to send them @@ -41,7 +42,7 @@ function RequireAuth({ visitor, children }) { /** * @param {PageProps} props */ -export function Discover({ visitor }) { +export function Discover() { const location = useLocation(); let search = qs.parse(location.search.substring(1)); const query = { @@ -49,24 +50,24 @@ export function Discover({ visitor }) { search: search, pageParam: search.search ? 'page' : 'before_mid' }; - return (<Feed query={query} visitor={visitor} />); + return (<Feed query={query} />); } /** * @param {PageProps} props */ -export function Discussions({ visitor }) { +export function Discussions() { const query = { baseUrl: '/api/messages/discussions', pageParam: 'to' }; - return (<Feed query={query} visitor={visitor} />); + return (<Feed query={query} />); } /** * @param {PageProps} props */ -export function Blog({ visitor }) { +export function Blog() { const { user } = useParams(); const location = useLocation(); const search = { @@ -83,7 +84,7 @@ export function Blog({ visitor }) { <div className="msg-cont"> <UserInfo uname={user} /> </div> - <Feed query={query} visitor={visitor} /> + <Feed query={query} /> </> ); } @@ -91,7 +92,7 @@ export function Blog({ visitor }) { /** * @param {PageProps} props */ -export function Tag({ visitor }) { +export function Tag() { const params = useParams(); const { tag } = params; const query = { @@ -101,27 +102,26 @@ export function Tag({ visitor }) { }, pageParam: 'before_mid' }; - return (<Feed query={query} visitor={visitor} />); + return (<Feed query={query} />); } /** * @param {PageProps} props */ -export function Home({ visitor }) { +export function Home() { const query = { baseUrl: '/api/home', pageParam: 'before_mid' }; return ( - <RequireAuth visitor={visitor}> - <Feed query={query} visitor={visitor} /> + <RequireAuth> + <Feed query={query} /> </RequireAuth> ); } /** * @typedef {object} FeedState - * @property { import('../api').SecureUser } visitor * @property { import('../api').Message[]= } msgs * @property { Query} query */ @@ -129,8 +129,9 @@ export function Home({ visitor }) { /** * @param {FeedState} props */ -function Feed({ visitor, query }) { +function Feed({ query }) { const location = useLocation(); + const [visitor] = useVisitor(); const [state, setState] = useState({ hash: visitor.hash, msgs: [], diff --git a/vnext/src/ui/Header.js b/vnext/src/ui/Header.js index 3162c9ea..db8959ea 100644 --- a/vnext/src/ui/Header.js +++ b/vnext/src/ui/Header.js @@ -4,8 +4,10 @@ import { Link, useNavigate } from 'react-router-dom'; import Icon from './Icon'; import { UserLink } from './UserInfo'; import SearchBox from './SearchBox'; +import { useVisitor } from './VisitorContext'; -function Header({ visitor, className }) { +function Header() { + const [visitor] = useVisitor(); const navigate = useNavigate(); /** * @param {string} searchString @@ -17,7 +19,7 @@ function Header({ visitor, className }) { navigate(location); }, [navigate]); return ( - <div id="header" className={className}> + <div id="header"> <div id="header_wrapper"> { visitor.uid < 0 ? diff --git a/vnext/src/ui/Login.js b/vnext/src/ui/Login.js index 5a96c822..98f9328b 100644 --- a/vnext/src/ui/Login.js +++ b/vnext/src/ui/Login.js @@ -8,22 +8,22 @@ import { useForm } from 'react-hook-form'; import { me, facebookLink, vkLink, appleLink } from '../api'; import './Login.css'; +import { useVisitor } from './VisitorContext'; /** - * @typedef {Object} LoginProps - * @property {import('../api').SecureUser} visitor - * @property {function} onAuth + * @typedef {object} LoginProps + * @property {Function} onAuth */ /** * Login page + * * @param {LoginProps} props */ -function Login({ visitor, onAuth }) { - +function Login({ onAuth }) { const location = useLocation(); const navigate = useNavigate(); - + const [visitor] = useVisitor(); useEffect(() => { if (visitor.hash) { const {retpath } = location.state || '/'; diff --git a/vnext/src/ui/Message.js b/vnext/src/ui/Message.js index dd6fc0ce..37d74312 100644 --- a/vnext/src/ui/Message.js +++ b/vnext/src/ui/Message.js @@ -8,11 +8,11 @@ import Avatar from './Avatar'; import { UserLink } from './UserInfo'; import { format, embedUrls } from '../utils/embed'; +import { useVisitor } from './VisitorContext'; /** * @typedef {object} MessageProps * @property { import('../client').Message } data data - * @property { import('../client').SecureUser } visitor visitor */ /** @@ -20,7 +20,8 @@ import { format, embedUrls } from '../utils/embed'; * * @param {React.PropsWithChildren<{}> & MessageProps} props props */ -export default function Message({ visitor, data, children }) { +export default function Message({ data, children }) { + const [visitor] = useVisitor(); const isCode = (data.tags || []).indexOf('code') >= 0; const likesSummary = data.likes ? `${data.likes}` : 'Recommend'; const commentsSummary = data.replies ? `${data.replies}` : 'Comment'; diff --git a/vnext/src/ui/PM.js b/vnext/src/ui/PM.js index 8885153d..d5a7eff1 100644 --- a/vnext/src/ui/PM.js +++ b/vnext/src/ui/PM.js @@ -3,9 +3,11 @@ import { memo } from 'react'; import Avatar from './Avatar'; import { format } from '../utils/embed'; import { chatItemStyle } from './helpers/BubbleStyle'; +import { useVisitor } from './VisitorContext'; function PM(props) { - const { chat, visitor } = props; + const { chat } = props; + const [visitor] = useVisitor(); return ( <li> <div style={chatItemStyle(visitor, chat)}> @@ -21,7 +23,6 @@ function PM(props) { export default memo(PM); /* PM.propTypes = { - chat: MessageType.isRequired, - visitor: UserType.isRequired + chat: MessageType.isRequired }; */ diff --git a/vnext/src/ui/Post.js b/vnext/src/ui/Post.js index a1d14ea0..51c25a19 100644 --- a/vnext/src/ui/Post.js +++ b/vnext/src/ui/Post.js @@ -7,13 +7,15 @@ import Button from './Button'; import MessageInput from './MessageInput'; import { post, update } from '../api'; +import { useVisitor } from './VisitorContext'; /** - * @param {{visitor: import('../api').User}} props + * */ -export default function Post({ visitor }) { +export default function Post() { const location = useLocation(); const navigate = useNavigate(); + const [visitor] = useVisitor(); let draftMessage = (location.state || {}).draft || {}; let [draft, setDraft] = useState(draftMessage.body); let params = qs.parse(window.location.search.substring(1)); diff --git a/vnext/src/ui/Settings.js b/vnext/src/ui/Settings.js index ccafffa3..3ae33567 100644 --- a/vnext/src/ui/Settings.js +++ b/vnext/src/ui/Settings.js @@ -6,11 +6,13 @@ import Button from './Button'; import Icon from './Icon'; import UploadButton from './UploadButton'; import Avatar from './Avatar'; +import { useVisitor } from './VisitorContext'; /** - * @param {{ visitor: import('../api').SecureUser, onChange: function }} props + * @param {{ onChange: Function }} props */ -function ChangeAvatarForm({ visitor, onChange }) { +function ChangeAvatarForm({ onChange }) { + const [visitor] = useVisitor(); const [avatar, setAvatar] = useState(''); const [preview, setPreview] = useState(); const avatarInput = useRef(); @@ -53,9 +55,11 @@ function ChangeAvatarForm({ visitor, onChange }) { } /** - * @param {{ visitor: import('../api').SecureUser, onChange: function }} props + * @param {{ onChange: Function }} props */ -export default function Settings({ visitor, onChange }) { +export default function Settings({ onChange }) { + + const [visitor] = useVisitor(); let passwordChanged = (event) => { console.log('password changed'); @@ -100,7 +104,7 @@ export default function Settings({ visitor, onChange }) { <div className="msg-cont"> <fieldset> <legend><Icon name="ei-user" size="m" />Changing your avatar</legend> - <ChangeAvatarForm visitor={visitor} onChange={onChange} /> + <ChangeAvatarForm onChange={onChange} /> </fieldset> <fieldset> <legend><Icon name="ei-unlock" size="m" />Changing your password</legend> diff --git a/vnext/src/ui/Thread.js b/vnext/src/ui/Thread.js index 5c9412d6..b2d17824 100644 --- a/vnext/src/ui/Thread.js +++ b/vnext/src/ui/Thread.js @@ -7,6 +7,7 @@ import MessageInput from './MessageInput'; import Spinner from './Spinner'; import { getMessages, comment, update } from '../api'; +import { useVisitor } from './VisitorContext'; /** * @type { import('../api').Message } */ @@ -14,7 +15,6 @@ const emptyMessage = {}; /** * @param {{ - visitor: import('../api').SecureUser connection: EventSource? }} props */ @@ -27,7 +27,8 @@ export default function Thread(props) { const [active, setActive] = useState(0); const [editing, setEditing] = useState(emptyMessage); - const [hash] = useState(props.visitor.hash); + const [visitor] = useVisitor(); + const [hash] = useState(visitor.hash); const { mid } = params; let loadReplies = useCallback(() => { @@ -97,7 +98,7 @@ export default function Thread(props) { <> { message.mid ? ( - <Message data={message} visitor={props.visitor}> + <Message data={message}> {active === (message.rid || 0) && <MessageInput data={message} text={editing.body || ''} onSend={postComment}>Write a comment...</MessageInput>} </Message> ) : ( @@ -109,7 +110,7 @@ export default function Thread(props) { { !loading ? replies.map((msg) => ( <li id={msg.rid} key={msg.rid}> - <Comment msg={msg} draft={msg.rid === editing.replyto ? editing.body : ''} visitor={props.visitor} active={active} setActive={setActive} onStartEditing={startEditing} postComment={postComment} /> + <Comment msg={msg} draft={msg.rid === editing.replyto ? editing.body : ''} active={active} setActive={setActive} onStartEditing={startEditing} postComment={postComment} /> </li> )) : ( <> diff --git a/vnext/src/ui/Users.js b/vnext/src/ui/Users.js index 2672c9cd..900cfaf0 100644 --- a/vnext/src/ui/Users.js +++ b/vnext/src/ui/Users.js @@ -22,6 +22,7 @@ export function Readers() { /** * UserInfo list component + * * @param {{uname: string, prop: string}} props */ function Users({ uname, prop }) { diff --git a/vnext/src/ui/VisitorContext.js b/vnext/src/ui/VisitorContext.js new file mode 100644 index 00000000..8c5364e7 --- /dev/null +++ b/vnext/src/ui/VisitorContext.js @@ -0,0 +1,32 @@ +import { createContext, useContext, useState } from 'react'; + +const Visitor = createContext(); + +/** @type {import('../api').SecureUser} */ +const unknownUser = { + uid: -1 +}; + +/** + * @param { import('react').PropsWithChildren<{}> } props + */ +export function VisitorProvider({ children }) { + const state = useState(unknownUser); + return <Visitor.Provider value={state}>{children}</Visitor.Provider>; +} + +/** + * Visitor hook + * + * @returns {[ + * import('../api').SecureUser, + * import('react').Dispatch<import('react').SetStateAction<import('../api').SecureUser>> + * ]} visitor hook + */ +export function useVisitor() { + const visitor = useContext(Visitor); + if (visitor === undefined) { + throw new Error('useVisitor must be used within a VisitorProvider'); + } + return visitor; +}
\ No newline at end of file |