Mobile layout fixes, added user forum, modal scroll fix

This commit is contained in:
mrfry 2022-05-16 16:20:02 +02:00
parent a8ec93a685
commit 9c333008c7
12 changed files with 649 additions and 428 deletions

View file

@ -34,14 +34,8 @@ export default function Question({ question, searchTerm }) {
return (
<div className="questionContainer">
<div
className="question"
dangerouslySetInnerHTML={{ __html: questionText }}
></div>
<div
className="answer"
dangerouslySetInnerHTML={{ __html: answerText }}
></div>
<div className="question">{questionText}</div>
<div className="answer">{answerText}</div>
<div className="data">{qdata || null}</div>
</div>
)

462
src/components/forum.js Normal file
View file

@ -0,0 +1,462 @@
import React, { useState, useEffect } from 'react'
import fetch from 'unfetch'
import LoadingIndicator from '../components/LoadingIndicator'
import Sleep from '../components/sleep'
import NewsEntry from '../components/newsEntry'
import Composer from '../components/composer'
import Header from '../components/header'
import Modal from '../components/modal'
import styles from './forum.module.css'
import constants from '../constants.json'
const forumPostPerPage = 5
function fetchEntry(postKey, forumName) {
return new Promise((resolve) => {
fetch(
`${constants.apiUrl}forumEntry?forumName=${forumName}&postKey=${postKey}`,
{
credentials: 'include',
}
)
.then((resp) => {
return resp.json()
})
.then((res) => {
resolve(res)
})
})
}
function fetchForum(forumName, from) {
return new Promise((resolve) => {
fetch(
`${constants.apiUrl}forumEntries?forumName=${forumName}&getContent=true${
from ? `&from=${from}` : ''
}&count=${forumPostPerPage}`,
{
credentials: 'include',
}
)
.then((resp) => {
return resp.json()
})
.then((res) => {
resolve(res)
})
})
}
function addPost(title, content, forumName) {
return new Promise((resolve) => {
fetch(constants.apiUrl + 'addPost', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
forumName: forumName,
title: title,
content: content,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
resolve(res)
})
})
}
function updateForumPost(forum, postKey, postData) {
return Object.keys(forum).reduce((acc, key) => {
const entry = forum[key]
if (key === postKey) {
acc = {
...acc,
[key]: postData,
}
} else {
acc = {
...acc,
[key]: entry,
}
}
return acc
}, {})
}
const NewsEntryContainer = ({
postKey,
setNews,
news,
userId,
newsEntryData,
onTitleClick,
forumName,
}) => {
const [error, setError] = useState(false)
useEffect(() => {
if (!newsEntryData && !error) {
fetchEntry(postKey, forumName)
.then((res) => {
const { success, entry, msg } = res
if (success) {
setNews({ [postKey]: entry, ...news })
} else {
alert(msg)
setError(true)
}
})
.catch((e) => {
console.error(e)
setError(true)
})
}
}, [])
if (!newsEntryData) {
return <LoadingIndicator />
}
if (error) {
return <div>Lil fuckup</div>
}
return (
<NewsEntry
onTitleClick={onTitleClick}
uid={userId}
key={postKey}
newsKey={postKey}
newsItem={newsEntryData}
onPostDelete={() => {
fetch(constants.apiUrl + 'rmPost', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
forumName: forumName,
postKey: postKey,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
const { success, msg } = res
if (success) {
setNews(
Object.keys(news).reduce((acc, key) => {
const entry = news[key]
if (key !== postKey) {
acc = {
...acc,
[key]: entry,
}
}
return acc
}, {})
)
} else {
alert(msg)
}
})
}}
onNewsReact={({ reaction, isDelete }) => {
fetch(constants.apiUrl + 'react', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
reaction: reaction,
postKey: postKey,
isDelete: isDelete,
forumName: forumName,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
setNews(updateForumPost(news, postKey, res.postData))
})
}}
onCommentReact={({ path, reaction, isDelete }) => {
fetch(constants.apiUrl + 'react', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'reaction',
postKey: postKey,
path: path,
reaction: reaction,
isDelete: isDelete,
forumName: forumName,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
const { success, postData, msg } = res
if (success) {
setNews(updateForumPost(news, postKey, postData))
} else {
alert(msg)
}
})
}}
onDelete={(path) => {
fetch(constants.apiUrl + 'comment', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'delete',
path: path,
postKey: postKey,
forumName: forumName,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
const { success, postData, msg } = res
if (success) {
setNews(updateForumPost(news, postKey, postData))
} else {
alert(msg)
}
})
}}
onComment={(path, content) => {
fetch(constants.apiUrl + 'comment', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'add',
path: path,
content: content,
postKey: postKey,
forumName: forumName,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
const { success, postData, msg } = res
if (success) {
setNews(updateForumPost(news, postKey, postData))
} else {
alert(msg)
}
})
}}
/>
)
}
export default function Forum({
router,
globalData,
globalState,
setGlobalState,
forumName,
children,
allowPost,
}) {
const userId = globalData.userId
const [news, setNews] = useState(null)
const [nextEntryKey, setNextEntryKey] = useState()
const [fetchingForum, setFetchingForum] = useState(false)
const [postInModalKey, setPostInModalKey] = useState()
const [isUploading, setIsUploading] = useState(false)
useEffect(() => {
if (globalState[forumName]) {
const { entries, nextKey } = globalState[forumName]
setNextEntryKey(nextKey)
setNews(entries)
} else {
setFetchingForum(true)
fetchForum(forumName).then((res) => {
setFetchingForum(false)
const { entries, nextKey } = res
setNextEntryKey(nextKey)
setNews(entries)
setGlobalState({ [forumName]: res })
})
}
}, [])
useEffect(() => {
const postKey = router.query.postKey
? decodeURIComponent(router.query.postKey)
: ''
if (postKey) {
setPostInModalKey(postKey)
}
}, [router.query.postKey])
const renderNews = () => {
if (news) {
const newsItems = Object.keys(news).map((postKey) => {
const newsEntryData = news[postKey]
return (
<NewsEntryContainer
forumName={forumName}
onTitleClick={() => {
setPostInModalKey(postKey)
router.replace(
`${router.pathname}?postKey=${encodeURIComponent(postKey)}`,
undefined,
{ shallow: true }
)
}}
key={postKey}
postKey={postKey}
setNews={setNews}
news={news}
userId={userId}
newsEntryData={newsEntryData}
/>
)
})
return (
<div>
{allowPost && (
<Composer
onSubmit={(title, content) => {
setIsUploading(true)
addPost(title, content, forumName).then((res) => {
const { success, newPostKey, newEntry, msg } = res
if (success) {
setNews({ [newPostKey]: newEntry, ...news })
} else {
alert(msg)
}
setIsUploading(false)
})
}}
/>
)}
{isUploading && (
<div
style={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
}}
>
{'Feltöltés ...'}
<LoadingIndicator />
</div>
)}
<div>{newsItems}</div>
{nextEntryKey ? (
<div
className={styles.loadMoreButton}
onClick={() => {
if (fetchingForum) {
return
}
setFetchingForum(true)
fetchForum(forumName, nextEntryKey).then((res) => {
setFetchingForum(false)
const { entries, nextKey } = res
setNextEntryKey(nextKey)
setNews({ ...news, ...entries })
setGlobalState({
news: {
entries: { ...news, ...entries },
nextKey: nextKey,
},
})
})
}}
>
{fetchingForum ? (
<LoadingIndicator />
) : (
'Több bejegyzés betöltése'
)}
</div>
) : (
<div
style={{
padding: 16,
display: 'flex',
justifyContent: 'center',
fontStyle: 'italic',
}}
>
{newsItems.length === 0 ? 'Üres fórum' : 'The end'}
</div>
)}
</div>
)
} else {
return <LoadingIndicator />
}
}
return (
<div>
<Header />
<Sleep />
{children || null}
{renderNews()}
{postInModalKey && (
<Modal
closeClick={() => {
setPostInModalKey(undefined)
router.replace(router.pathname, undefined, { shallow: true })
}}
>
{news ? (
<NewsEntryContainer
postKey={postInModalKey}
setNews={setNews}
news={news}
userId={userId}
newsEntryData={news[postInModalKey]}
/>
) : (
<LoadingIndicator />
)}
</Modal>
)}
</div>
)
}

View file

@ -0,0 +1,97 @@
.hr {
width: 100%;
}
.motd {
text-align: center;
font-size: 20px;
border: 2px dashed var(--text-color);
padding-top: 13px;
padding-bottom: 15px;
padding-left: 5px;
padding-right: 5px;
margin-top: 18px;
margin-bottom: 30px;
margin-left: 5px;
margin-right: 5px;
}
.itemContainer {
width: 100%;
margin: 20px 5px;
background-color: var(--hoover-color);
}
.newsBody {
margin: 0px 5px;
padding: 10px 14px;
font-size: 17px;
color: #fff;
text-align: justify;
}
.title {
color: var(--text-color);
font-size: 32px;
text-align: center;
letter-spacing: 2.5px;
}
.subtitle {
color: var(--text-color);
font-size: 20px;
text-align: center;
}
.newsTitle {
color: var(--text-color);
font-size: 28px;
padding-left: 17px;
}
.question {
font-weight: bold;
font-size: 16px;
color: #fff;
margin: 0px 5px;
}
.answer {
margin: 0px 5px;
}
.itemNumber {
color: #a7a7a7;
margin: 0px 5px;
font-size: 22px;
padding-top: 12px;
padding-left: 13px;
padding-bottom: 3px;
}
.repos {
display: flex;
flex-direction: column;
}
.loadMoreButton {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--dark-color);
margin-left: 8px;
margin-right: 8px;
margin-bottom: 16px;
margin-top: 16px;
padding: 10px;
height: 50px;
cursor: pointer;
}
.loadMoreButton:hover {
background-color: var(--hoover-color);
}

View file

@ -98,6 +98,7 @@
@media screen and (max-width: 700px) {
div.content {
max-width: calc(100vw);
padding: 1px 0px;
margin-left: 0;
}

View file

@ -13,8 +13,10 @@ export default function Modal(props) {
useEffect(() => {
document.addEventListener('keydown', keyHandler)
document.body.classList.add('modal-open')
return () => {
document.removeEventListener('keydown', keyHandler)
document.body.classList.remove('modal-open')
}
}, [])

View file

@ -125,6 +125,8 @@ export default function NewsEntry({
{uid === user ? (
<span
onClick={() => {
const res = window.confirm('Törlöd a bejegyzést?')
if (!res) return
onPostDelete()
}}
>

View file

@ -1,7 +1,11 @@
{
"index": {
"href": "/",
"text": "Főoldal"
"text": "Hírek"
},
"userForum": {
"href": "/userForum",
"text": "Fórum"
},
"script": {
"href": "/script",

View file

@ -102,19 +102,16 @@ input:focus {
}
.question {
word-wrap: break-word;
font-weight: bold;
font-size: 17px;
color: gainsboro;
}
.answer {
word-wrap: break-word;
font-size: 15px;
}
.data {
word-wrap: break-word;
font-size: 13px;
color: #a1a1a1;
}
@ -355,3 +352,7 @@ select:hover {
font-weight: bold;
color: var(--text-color);
}
.modal-open {
overflow: hidden;
}

View file

@ -1,331 +1,26 @@
import React, { useState, useEffect } from 'react'
import fetch from 'unfetch'
import React from 'react'
import LoadingIndicator from '../components/LoadingIndicator'
import Sleep from '../components/sleep'
import NewsEntry from '../components/newsEntry'
import Composer from '../components/composer'
import Header from '../components/header'
import Forum from '../components/forum'
import styles from './index.module.css'
import constants from '../constants.json'
const forumPostPerPage = 5
const frontpageForumName = 'frontpage'
function fetchForum(from) {
return new Promise((resolve) => {
fetch(
`${
constants.apiUrl
}forumEntries?forumName=${frontpageForumName}&getContent=true${
from ? `&from=${from}` : ''
}&count=${forumPostPerPage}`,
{
credentials: 'include',
}
)
.then((resp) => {
return resp.json()
})
.then((res) => {
resolve(res)
})
})
}
function addPost(title, content) {
return new Promise((resolve) => {
fetch(constants.apiUrl + 'addPost', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
forumName: frontpageForumName,
title: title,
content: content,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
resolve(res)
})
})
}
function updateForumPost(forum, postKey, postData) {
return Object.keys(forum).reduce((acc, key) => {
const entry = forum[key]
if (key === postKey) {
acc = {
...acc,
[key]: postData,
}
} else {
acc = {
...acc,
[key]: entry,
}
}
return acc
}, {})
}
export default function Main({ globalData, globalState, setGlobalState }) {
const userId = globalData.userId
export default function Index({
router,
globalData,
globalState,
setGlobalState,
}) {
const motd = globalData.motd
const [news, setNews] = useState(null)
const [nextEntryKey, setNextEntryKey] = useState()
const [fetchingForum, setFetchingForum] = useState(false)
useEffect(() => {
if (globalState.news) {
const { entries, nextKey } = globalState.news
setNextEntryKey(nextKey)
setNews(entries)
} else {
setFetchingForum(true)
fetchForum().then((res) => {
setFetchingForum(false)
const { entries, nextKey } = res
setNextEntryKey(nextKey)
setNews(entries)
setGlobalState({ news: res })
})
}
}, [])
const renderNews = () => {
if (news) {
let newsItems = Object.keys(news).map((postKey) => {
let newsEntryData = news[postKey]
return (
<NewsEntry
hideAdminIndicator
onPostDelete={() => {
fetch(constants.apiUrl + 'rmPost', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
forumName: frontpageForumName,
postKey: postKey,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
const { success, msg } = res
if (success) {
setNews(
Object.keys(news).reduce((acc, key) => {
const entry = news[key]
if (key !== postKey) {
acc = {
...acc,
[key]: entry,
}
}
return acc
}, {})
)
} else {
alert(msg)
}
})
}}
onNewsReact={({ reaction, isDelete }) => {
fetch(constants.apiUrl + 'react', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
reaction: reaction,
postKey: postKey,
isDelete: isDelete,
forumName: frontpageForumName,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
setNews(updateForumPost(news, postKey, res.postData))
})
}}
onCommentReact={({ path, reaction, isDelete }) => {
fetch(constants.apiUrl + 'react', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'reaction',
postKey: postKey,
path: path,
reaction: reaction,
isDelete: isDelete,
forumName: frontpageForumName,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
const { success, postData, msg } = res
if (success) {
setNews(updateForumPost(news, postKey, postData))
} else {
alert(msg)
}
})
}}
onDelete={(path) => {
fetch(constants.apiUrl + 'comment', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'delete',
path: path,
postKey: postKey,
forumName: frontpageForumName,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
const { success, postData, msg } = res
if (success) {
setNews(updateForumPost(news, postKey, postData))
} else {
alert(msg)
}
})
}}
onComment={(path, content) => {
fetch(constants.apiUrl + 'comment', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'add',
path: path,
content: content,
postKey: postKey,
forumName: frontpageForumName,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
const { success, postData, msg } = res
if (success) {
setNews(updateForumPost(news, postKey, postData))
} else {
alert(msg)
}
})
}}
uid={userId}
key={postKey}
newsKey={postKey}
newsItem={newsEntryData}
/>
)
})
return (
<div>
<div className={styles.title}>Fórum/Hírek</div>
<hr />
<Composer
onSubmit={(title, content) => {
addPost(title, content).then((res) => {
const { success, newPostKey, newEntry, msg } = res
if (success) {
setNews({ [newPostKey]: newEntry, ...news })
} else {
alert(msg)
}
})
}}
/>
<div>{newsItems}</div>
{nextEntryKey ? (
<div
className={styles.loadMoreButton}
onClick={() => {
if (fetchingForum) {
return
}
setFetchingForum(true)
fetchForum(nextEntryKey).then((res) => {
setFetchingForum(false)
const { entries, nextKey } = res
setNextEntryKey(nextKey)
setNews({ ...news, ...entries })
setGlobalState({
news: {
entries: { ...news, ...entries },
nextKey: nextKey,
},
})
})
}}
>
{fetchingForum ? (
<LoadingIndicator />
) : (
'Több bejegyzés betöltése'
)}
</div>
) : (
<div
style={{
padding: 16,
display: 'flex',
justifyContent: 'center',
fontStyle: 'italic',
}}
>
{'The end'}
</div>
)}
</div>
)
} else {
return <LoadingIndicator />
}
}
const renderMotd = () => {
if (motd) {
return (
return (
<Forum
router={router}
globalState={globalState}
setGlobalState={setGlobalState}
globalData={globalData}
forumName={'frontpage'}
>
{motd && (
<div className={styles.motd}>
<div className={styles.title}>MOTD</div>
{motd ? (
@ -334,18 +29,7 @@ export default function Main({ globalData, globalState, setGlobalState }) {
<div>...</div>
)}
</div>
)
} else {
return null
}
}
return (
<div>
<Header />
{renderMotd()}
<Sleep />
{renderNews()}
</div>
)}
</Forum>
)
}

View file

@ -1,5 +1,8 @@
.hr {
width: 100%;
.title {
color: var(--text-color);
font-size: 32px;
text-align: center;
letter-spacing: 2.5px;
}
.motd {
@ -16,82 +19,3 @@
margin-left: 5px;
margin-right: 5px;
}
.itemContainer {
width: 100%;
margin: 20px 5px;
background-color: var(--hoover-color);
}
.newsBody {
margin: 0px 5px;
padding: 10px 14px;
font-size: 17px;
color: #fff;
text-align: justify;
}
.title {
color: var(--text-color);
font-size: 32px;
text-align: center;
letter-spacing: 2.5px;
}
.subtitle {
color: var(--text-color);
font-size: 20px;
text-align: center;
}
.newsTitle {
color: var(--text-color);
font-size: 28px;
padding-left: 17px;
}
.question {
font-weight: bold;
font-size: 16px;
color: #fff;
margin: 0px 5px;
}
.answer {
margin: 0px 5px;
}
.itemNumber {
color: #a7a7a7;
margin: 0px 5px;
font-size: 22px;
padding-top: 12px;
padding-left: 13px;
padding-bottom: 3px;
}
.repos {
display: flex;
flex-direction: column;
}
.loadMoreButton {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--dark-color);
margin-left: 8px;
margin-right: 8px;
margin-bottom: 16px;
margin-top: 16px;
padding: 10px;
height: 50px;
cursor: pointer;
}
.loadMoreButton:hover {
background-color: var(--hoover-color);
}

29
src/pages/userForum.js Normal file
View file

@ -0,0 +1,29 @@
import React from 'react'
import Forum from '../components/forum'
import styles from './userForum.module.css'
export default function UserForum({
router,
globalData,
globalState,
setGlobalState,
}) {
return (
<Forum
allowPost
router={router}
globalState={globalState}
setGlobalState={setGlobalState}
globalData={globalData}
forumName={'userForum'}
>
<div className={styles.topMsg}>
<div className={styles.title}>Felhasználói fórum</div>
Itt lehet témákat felvetni és megbeszélni a többi felhasználóval, vagy
adminnal.
</div>
</Forum>
)
}

View file

@ -0,0 +1,21 @@
.topMsg {
text-align: center;
font-size: 18px;
border: 2px dashed var(--text-color);
padding-top: 13px;
padding-bottom: 15px;
padding-left: 5px;
padding-right: 5px;
margin-top: 18px;
margin-bottom: 30px;
margin-left: 5px;
margin-right: 5px;
}
.title {
color: var(--text-color);
font-size: 32px;
text-align: center;
letter-spacing: 2.5px;
}