UNPKG

@state-less/leap-frontend

Version:

A collection of open source fullstack services powered by React Server

150 lines (149 loc) 10 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Container, TextField, Button, Box, Card, CardHeader, CardContent, Chip, CardActions, Alert, LinearProgress, IconButton, Link, Typography, } from '@mui/material'; import { authContext, useComponent, useLocalStorage, } from '@state-less/react-client'; import { useParams, Link as RouterLink } from 'react-router-dom'; import { useEffect, useState, useRef, useContext } from 'react'; import { CommunityComments } from '../server-components/examples/Comments.js'; import { useSyncedState } from '../lib/hooks.js'; import { ViewCounter } from '../server-components/examples/ViewCounter.js'; import { UpDownButtons } from '../server-components/examples/VotingApp.js'; import { FlexBox } from '../components/FlexBox.js'; import { AnswerActions, ContentEditor, OwnerChip, PostActions, } from '../server-components/ContentEditor.js'; import { NewPost } from './newPost.js'; import { NewPostButton } from './index.js'; import { GoogleLoginButton } from '../components/LoggedInGoogleButton.js'; import { Home, Visibility } from '@mui/icons-material'; import { Helmet } from 'react-helmet'; export const PostsPage = ({ basePath = '', forumKey, clientId, onTitleLeave, settings: { showPostBC, forumTitle, renderMetaTags } = {}, ssr, }) => { const params = useParams(); if (params.post === 'new') { return _jsx(NewPost, { forumKey: forumKey, ssr: ssr }); } return (_jsxs(Container, { maxWidth: "lg", disableGutters: true, sx: { py: 4 }, children: [params.post && (_jsx(Post, { id: params.post, basePath: basePath, onTitleLeave: onTitleLeave, showBC: showPostBC, settings: { forumTitle, renderMetaTags }, ssr: ssr })), _jsx(ComposeAnswer, { id: params.post, clientId: clientId, ssr: ssr })] })); }; const DRAFT = true; const Post = ({ id, basePath, onTitleLeave, showBC = false, settings: { forumTitle, renderMetaTags } = {}, ssr, }) => { // const { dispatch } = useContext(stateContext); const [_, setSkip] = useState(false); const [component, { error, loading }] = useComponent(id, { suspend: true, ssr, }); useEffect(() => { /* Skip recreated ViewCounter component as long as the post is in the cache*/ if (component?.props) setSkip?.(true); }, [component?.props, setSkip]); const [edit, setEdit] = useState(0); const [showDeleted, setShowDeleted] = useLocalStorage('mod-show-deleted', false); const [bodyServer, setBodyServer, { loading: bodyLoading }] = useSyncedState(component?.props?.body, component?.props?.setBody, { draft: DRAFT, }); const ref = useRef(null); useEffect(() => { if (!ref?.current) return; const obs = new IntersectionObserver((entries) => { if (entries[0]?.isIntersecting) { onTitleLeave?.(true); // dispatch({ type: Actions.SET_LAST_BC, value: true }); } else if (entries[0]?.boundingClientRect?.y > 0) { onTitleLeave?.(false); // dispatch({ type: Actions.SET_LAST_BC, value: false }); } }, { root: document.body, rootMargin: '0px 0px -100%', threshold: 0.0, }); obs.observe(ref?.current); }, [ref?.current]); const [body, setBody] = useState(bodyServer); useEffect(() => { setBody(bodyServer); }, [bodyServer]); if (error) return null; if (loading) return (_jsxs(_Fragment, { children: [_jsx(Alert, { severity: "info", children: "Loading..." }), _jsx(LinearProgress, { variant: "indeterminate" })] })); const title = component?.props?.title || 'Post'; return (_jsxs("div", { ref: ref, children: [_jsxs(FlexBox, { sx: { alignItems: 'center', height: 'min-content', flexWrap: 'wrap-reverse', }, children: [renderMetaTags && (_jsxs(Helmet, { children: [_jsx("title", { children: `${title} | ${forumTitle}` }), _jsx("meta", { property: "og:title", content: `${title} | ${forumTitle}` }), _jsx("meta", { name: "twitter:title", content: `${title} | ${forumTitle}` })] })), _jsx(CardHeader, { title: showBC ? (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center' }, children: [_jsx(Link, { component: RouterLink, sx: { color: 'black', display: 'flex' }, to: '/', children: _jsx(Home, { sx: { width: '1.4em', height: '1.4em' } }) }), "/", _jsx(Typography, { variant: "h4", sx: { color: 'black' }, children: title })] })) : (title) }), _jsxs(FlexBox, { sx: { ml: 'auto', mr: { xs: 'auto', sm: 'unset' }, px: 2, flexDirection: { xs: 'column-reverse', sm: 'row' }, }, children: [_jsx(IconButton, { color: showDeleted ? 'info' : undefined, onClick: () => setShowDeleted(!showDeleted), children: _jsx(Visibility, {}) }), _jsx(NewPostButton, { basePath: basePath })] })] }), _jsxs(Card, { sx: { mb: 1 }, color: "info", children: [component?.props?.deleted && (_jsx(Alert, { severity: "error", children: "This post has been deleted." })), !component?.props?.canDelete && !component?.props?.approved && (_jsx(Alert, { severity: "info", children: "This post needs approval from an admin." })), _jsxs(FlexBox, { sx: { maxWidth: '100%', flexDirection: { xs: 'column', sm: 'row', }, }, children: [component?.children[0] && (_jsx(UpDownButtons, { data: component?.children[0], id: component?.children[0]?.component, wilson: false })), _jsx(ContentEditor, { draft: DRAFT, body: DRAFT ? body : bodyServer, component: component, edit: edit > 0, loading: bodyLoading, setBody: DRAFT ? setBody : setBodyServer, setEdit: async (e) => { if (!e) { await setBodyServer(body); setEdit(0); } else { setEdit(2); } } })] }), _jsx(PostActions, { draft: DRAFT, component: component, edit: edit, setEdit: async (e) => { if (!e) { await setBodyServer(body); setEdit(0); } else { setEdit(2); } } }), component?.props.tags?.length > 0 && (_jsx(CardContent, { sx: { display: 'flex', gap: 1 }, children: component?.props.tags?.map((tag) => (_jsx(Chip, { color: "info", label: tag }))) }))] }), component?.props.viewCounter && (_jsx(ViewCounter, { componentKey: component?.props.viewCounter?.component, ssr: ssr })), component?.children .filter((c) => c?.props?.body && (showDeleted ? true : !c?.props?.deleted)) ?.map((answer) => { return _jsx(Answer, { answer: answer, ssr: ssr }); })] })); }; const Answer = ({ answer, ssr }) => { const [component] = useComponent(answer?.component, { suspend: true, ssr, data: answer, }); const [edit, setEdit] = useState(0); const [bodyServer, setBodyServer, { loading: bodyLoading }] = useSyncedState(component?.props?.body, component?.props?.setBody); const [body, setBody] = useState(bodyServer); return (_jsxs(Card, { sx: { mb: 1 }, color: "info", children: [_jsxs(FlexBox, { children: [_jsx(UpDownButtons, { id: answer?.children[0]?.component, data: answer?.children[0], wilson: false }), _jsxs(Box, { sx: { width: '100%' }, children: [component?.props?.deleted && (_jsx(Alert, { severity: "error", children: "This post has been deleted." })), _jsx(ContentEditor, { draft: DRAFT, body: DRAFT ? body : bodyServer, component: component, edit: edit > 0, loading: bodyLoading, setBody: DRAFT ? setBody : setBodyServer, setEdit: async (e) => { if (!e) { await setBodyServer(body); setEdit(0); } else { setEdit(2); } } })] })] }), _jsx(AnswerActions, { component: component, edit: edit, setEdit: async (e) => { if (!e) { await setBodyServer(body); setEdit(0); } else { setEdit(2); } }, draft: DRAFT, ssr: ssr }), _jsx(CommunityComments, { id: answer?.children[1]?.component, ssr: ssr })] })); }; const ComposeAnswer = ({ id, clientId, ssr }) => { const { session } = useContext(authContext); const [component] = useComponent(id, { suspend: true, ssr, }); const [body, setBody] = useState(''); return (_jsxs(Card, { sx: { p: 2 }, children: [_jsx(TextField, { multiline: true, fullWidth: true, label: "Answer", rows: 7, onChange: (e) => setBody(e.target.value), value: body }), _jsxs(CardActions, { children: [_jsx(Button, { disabled: body.length === 0, onClick: async () => { setBody(''); await component?.props?.createAnswer({ body, }); }, children: "Post Answer" }), session?.id && (_jsx(OwnerChip, { sx: { marginLeft: 1 }, owner: session?.strategies?.[session?.strategy || '']?.decoded })), !session?.id && _jsx(GoogleLoginButton, { clientId: clientId })] })] })); }; export default PostsPage;