@state-less/leap-frontend
Version:
A collection of open source fullstack services powered by React Server
150 lines (149 loc) • 10 kB
JavaScript
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;