mirror of
				https://gitlab.com/MrFry/qmining-page
				synced 2025-04-01 20:23:44 +02:00 
			
		
		
		
	Merged master
This commit is contained in:
		@@ -18,7 +18,7 @@ module.exports = {
 | 
			
		||||
    'no-prototype-builtins': 'off',
 | 
			
		||||
    'id-length': [
 | 
			
		||||
      'warn',
 | 
			
		||||
      { exceptions: ['x', 'i', 'j', 't', 'Q', 'A', 'C', 'q', 'a', 'b'] },
 | 
			
		||||
      { exceptions: ['x', 'i', 'j', 't', 'Q', 'A', 'C', 'q', 'a', 'b', 'e'] },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  root: true,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
node_modules/
 | 
			
		||||
.next/
 | 
			
		||||
out/
 | 
			
		||||
 | 
			
		||||
/.vs
 | 
			
		||||
public/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										227
									
								
								src/components/comments.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								src/components/comments.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,227 @@
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
 | 
			
		||||
import ReactButton from './reactButton.js'
 | 
			
		||||
import Modal from './modal.js'
 | 
			
		||||
 | 
			
		||||
import styles from './comments.module.css'
 | 
			
		||||
 | 
			
		||||
function CommentInput({ onSubmit, onCancel }) {
 | 
			
		||||
  const [val, setVal] = useState('')
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.commentAreaContainer}>
 | 
			
		||||
      <textarea
 | 
			
		||||
        autoFocus
 | 
			
		||||
        value={val}
 | 
			
		||||
        onChange={(e) => {
 | 
			
		||||
          setVal(e.target.value)
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      <div className={'actions'}>
 | 
			
		||||
        <span
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            onSubmit(val)
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          Submit
 | 
			
		||||
        </span>
 | 
			
		||||
        <span
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            onCancel()
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          Cancel
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function Comment({ comment, index, onComment, onDelete, onReact, uid }) {
 | 
			
		||||
  const [displayed, setDisplayed] = useState(true)
 | 
			
		||||
  const [commenting, setCommenting] = useState(false)
 | 
			
		||||
  const { content, subComments, date, user, reacts, admin } = comment
 | 
			
		||||
  const own = uid === user
 | 
			
		||||
 | 
			
		||||
  const commentStyle = admin
 | 
			
		||||
    ? styles.adminComment
 | 
			
		||||
    : own
 | 
			
		||||
    ? styles.ownComment
 | 
			
		||||
    : ''
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.comment}>
 | 
			
		||||
      <div className={`${styles.commentData} ${commentStyle}`}>
 | 
			
		||||
        <div className={styles.commentHeader}>
 | 
			
		||||
          <div className={styles.userContainer}>
 | 
			
		||||
            <div
 | 
			
		||||
              className={styles.showHide}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                setDisplayed(!displayed)
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {displayed ? '[-]' : '[+]'}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>User #{user}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div>{date}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className={`${!displayed && styles.hidden}`}>
 | 
			
		||||
          <div className={styles.commentText}> {content}</div>
 | 
			
		||||
          <div className={'actions'}>
 | 
			
		||||
            <span
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                setCommenting(true)
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              Reply...
 | 
			
		||||
            </span>
 | 
			
		||||
            {own && (
 | 
			
		||||
              <span
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  onDelete([index])
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                Delete
 | 
			
		||||
              </span>
 | 
			
		||||
            )}
 | 
			
		||||
            <ReactButton
 | 
			
		||||
              onClick={(reaction, isDelete) => {
 | 
			
		||||
                onReact([index], reaction, isDelete)
 | 
			
		||||
              }}
 | 
			
		||||
              uid={uid}
 | 
			
		||||
              existingReacts={reacts}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          {commenting && (
 | 
			
		||||
            <CommentInput
 | 
			
		||||
              onCancel={() => {
 | 
			
		||||
                setCommenting(false)
 | 
			
		||||
              }}
 | 
			
		||||
              onSubmit={(e) => {
 | 
			
		||||
                onComment([index], e)
 | 
			
		||||
                setCommenting(false)
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className={`${!displayed && styles.hidden}`}>
 | 
			
		||||
        {subComments &&
 | 
			
		||||
          subComments.map((sc, i) => {
 | 
			
		||||
            return (
 | 
			
		||||
              <Comment
 | 
			
		||||
                comment={sc}
 | 
			
		||||
                onReact={(path, reaction, isDelete) => {
 | 
			
		||||
                  onReact([...path, index], reaction, isDelete)
 | 
			
		||||
                }}
 | 
			
		||||
                onDelete={(path) => {
 | 
			
		||||
                  onDelete([...path, index])
 | 
			
		||||
                }}
 | 
			
		||||
                onComment={(path, content) => {
 | 
			
		||||
                  onComment([...path, index], content)
 | 
			
		||||
                }}
 | 
			
		||||
                index={i}
 | 
			
		||||
                key={i}
 | 
			
		||||
                uid={uid}
 | 
			
		||||
              />
 | 
			
		||||
            )
 | 
			
		||||
          })}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function countComments(comments) {
 | 
			
		||||
  return comments.reduce((acc, comment) => {
 | 
			
		||||
    if (comment.subComments) {
 | 
			
		||||
      acc += countComments(comment.subComments) + 1
 | 
			
		||||
    } else {
 | 
			
		||||
      acc += 1
 | 
			
		||||
    }
 | 
			
		||||
    return acc
 | 
			
		||||
  }, 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function Comments({
 | 
			
		||||
  comments,
 | 
			
		||||
  onComment,
 | 
			
		||||
  onDelete,
 | 
			
		||||
  onReact,
 | 
			
		||||
  uid,
 | 
			
		||||
}) {
 | 
			
		||||
  const [addingNewComment, setAddingNewComment] = useState(false)
 | 
			
		||||
  const [commentsShowing, setCommentsShowing] = useState(false)
 | 
			
		||||
  const commentCount = comments ? countComments(comments) : 0
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      {commentsShowing ? (
 | 
			
		||||
        <Modal
 | 
			
		||||
          closeClick={() => {
 | 
			
		||||
            setCommentsShowing(false)
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {comments && comments.length > 0
 | 
			
		||||
            ? comments.map((comment, i) => {
 | 
			
		||||
                return (
 | 
			
		||||
                  <Comment
 | 
			
		||||
                    onReact={onReact}
 | 
			
		||||
                    onComment={onComment}
 | 
			
		||||
                    onDelete={onDelete}
 | 
			
		||||
                    comment={comment}
 | 
			
		||||
                    index={i}
 | 
			
		||||
                    key={i}
 | 
			
		||||
                    uid={uid}
 | 
			
		||||
                  />
 | 
			
		||||
                )
 | 
			
		||||
              })
 | 
			
		||||
            : null}
 | 
			
		||||
          {commentCount !== 0 ? (
 | 
			
		||||
            <div className={'actions'}>
 | 
			
		||||
              <span
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  setAddingNewComment(true)
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                New comment
 | 
			
		||||
              </span>
 | 
			
		||||
            </div>
 | 
			
		||||
          ) : null}
 | 
			
		||||
          {addingNewComment ? (
 | 
			
		||||
            <CommentInput
 | 
			
		||||
              onSubmit={(e) => {
 | 
			
		||||
                if (!e) {
 | 
			
		||||
                  alert('Írj be valamit, hogy kommentelhess...')
 | 
			
		||||
                  return
 | 
			
		||||
                }
 | 
			
		||||
                setAddingNewComment(false)
 | 
			
		||||
                onComment([], e)
 | 
			
		||||
              }}
 | 
			
		||||
              onCancel={() => {
 | 
			
		||||
                setAddingNewComment(false)
 | 
			
		||||
                if (commentCount === 0) {
 | 
			
		||||
                  setCommentsShowing(false)
 | 
			
		||||
                }
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          ) : null}
 | 
			
		||||
        </Modal>
 | 
			
		||||
      ) : null}
 | 
			
		||||
      <div
 | 
			
		||||
        className={'actions'}
 | 
			
		||||
        onClick={() => {
 | 
			
		||||
          setCommentsShowing(true)
 | 
			
		||||
          if (commentCount === 0) {
 | 
			
		||||
            setAddingNewComment(true)
 | 
			
		||||
          }
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <span>
 | 
			
		||||
          {commentCount === 0
 | 
			
		||||
            ? 'New comment'
 | 
			
		||||
            : `Show ${commentCount} comment${commentCount > 1 ? 's' : ''}`}
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								src/components/comments.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/components/comments.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
.comment {
 | 
			
		||||
  margin-left: 25px;
 | 
			
		||||
  padding: 8px 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.commentData {
 | 
			
		||||
  padding: 5px 2px;
 | 
			
		||||
  border-left: 2px solid var(--text-color);
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.commentData:hover {
 | 
			
		||||
  background-color: var(--hoover-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.commentHeader {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.commentHeader > div {
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  margin: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.userContainer {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.userContainer > div {
 | 
			
		||||
  padding: 0px 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.commentText {
 | 
			
		||||
  margin: 2px;
 | 
			
		||||
  padding: 10px 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ownComment {
 | 
			
		||||
  border-left: 2px solid green;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.showHide {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hidden {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.commentAreaContainer {
 | 
			
		||||
  margin: 0px 8px 3px 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.commentAreaContainer > div {
 | 
			
		||||
  margin: 5px 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.adminComment {
 | 
			
		||||
  border-left: 2px solid yellow;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								src/components/composer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/components/composer.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
 | 
			
		||||
import Modal from './modal'
 | 
			
		||||
 | 
			
		||||
import styles from './composer.module.css'
 | 
			
		||||
 | 
			
		||||
function FileUploader({ onChange }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.inputArea}>
 | 
			
		||||
      <div className={styles.textTitle}>Fájl csatolása</div>
 | 
			
		||||
      <input
 | 
			
		||||
        className={styles.fileInput}
 | 
			
		||||
        type="file"
 | 
			
		||||
        name="file"
 | 
			
		||||
        onChange={onChange}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function Composer({ onSubmit }) {
 | 
			
		||||
  const [editorShowing, setEditorShowing] = useState(false)
 | 
			
		||||
  const [val, setVal] = useState('')
 | 
			
		||||
  const [type, setType] = useState('public')
 | 
			
		||||
  const [title, setTitle] = useState('')
 | 
			
		||||
  const [file, setFile] = useState()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div
 | 
			
		||||
        onClick={() => {
 | 
			
		||||
          setEditorShowing(true)
 | 
			
		||||
        }}
 | 
			
		||||
        className={styles.new}
 | 
			
		||||
      >
 | 
			
		||||
        Új bejegyzés / feedback
 | 
			
		||||
      </div>
 | 
			
		||||
      {editorShowing && (
 | 
			
		||||
        <Modal
 | 
			
		||||
          closeClick={() => {
 | 
			
		||||
            setEditorShowing(false)
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <div className={styles.container}>
 | 
			
		||||
            {type !== 'private' && (
 | 
			
		||||
              <input
 | 
			
		||||
                placeholder={'Téma'}
 | 
			
		||||
                value={title}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  setTitle(e.target.value)
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            <textarea
 | 
			
		||||
              placeholder={'...'}
 | 
			
		||||
              value={val}
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                setVal(e.target.value)
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
            {type === 'private' && (
 | 
			
		||||
              <FileUploader
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  setFile(e.target.files[0])
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            <div className={styles.typeSelector}>
 | 
			
		||||
              <div>Post típusa:</div>
 | 
			
		||||
              <div title="Minden felhasználó látja főoldalon, és tudnak rá válaszolni">
 | 
			
		||||
                <input
 | 
			
		||||
                  onChange={(e) => {
 | 
			
		||||
                    setType(e.target.value)
 | 
			
		||||
                  }}
 | 
			
		||||
                  type="radio"
 | 
			
		||||
                  name="type"
 | 
			
		||||
                  value="public"
 | 
			
		||||
                  defaultChecked
 | 
			
		||||
                />
 | 
			
		||||
                Publikus
 | 
			
		||||
              </div>
 | 
			
		||||
              <div title="Csak a weboldal üzemeltetője látja">
 | 
			
		||||
                <input
 | 
			
		||||
                  onChange={(e) => {
 | 
			
		||||
                    setType(e.target.value)
 | 
			
		||||
                  }}
 | 
			
		||||
                  type="radio"
 | 
			
		||||
                  name="type"
 | 
			
		||||
                  value="private"
 | 
			
		||||
                />
 | 
			
		||||
                Privát
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className={styles.tip}>
 | 
			
		||||
                (Tartsd egered opciókra több infóért)
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={'actions'}>
 | 
			
		||||
              <span
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  onSubmit(type, title, val, file)
 | 
			
		||||
                  setEditorShowing(false)
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                Post
 | 
			
		||||
              </span>
 | 
			
		||||
              <span
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  setEditorShowing(false)
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                Cancel
 | 
			
		||||
              </span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Modal>
 | 
			
		||||
      )}
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/components/composer.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/components/composer.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
.container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-flow: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container > input,
 | 
			
		||||
.container > textarea {
 | 
			
		||||
  margin: 5px 0px;
 | 
			
		||||
  padding: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.typeSelector {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tip {
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
  margin: 0px 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.new {
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  border: 2px dashed var(--text-color);
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.new:hover {
 | 
			
		||||
  background-color: var(--hoover-color);
 | 
			
		||||
}
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.listItem:hover {
 | 
			
		||||
  background-color: #666;
 | 
			
		||||
  background-color: var(--hoover-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text {
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ export default function Modal(props) {
 | 
			
		||||
            ❌
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
        {props.children}
 | 
			
		||||
        <div className={styles.children}>{props.children}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
.modal {
 | 
			
		||||
  z-index: 9999;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
@@ -8,16 +9,19 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modalContent {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: stretch;
 | 
			
		||||
  max-height: 80%;
 | 
			
		||||
  width: 80%;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  background: var(--background-color);
 | 
			
		||||
  width: 70%;
 | 
			
		||||
  height: auto;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
  transform: translate(-50%, -50%);
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
  padding: 20px 30px;
 | 
			
		||||
  cursor: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -26,8 +30,14 @@
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 10px;
 | 
			
		||||
  right: 10px;
 | 
			
		||||
  display: inline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.children {
 | 
			
		||||
  max-height: 100%;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  overflow-y: scroll;
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								src/components/newsEntry.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/components/newsEntry.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
 | 
			
		||||
import ReactButton from './reactButton.js'
 | 
			
		||||
import Comments from './comments.js'
 | 
			
		||||
 | 
			
		||||
import styles from './newsEntry.module.css'
 | 
			
		||||
 | 
			
		||||
export default function NewsEntry({
 | 
			
		||||
  newsItem,
 | 
			
		||||
  uid,
 | 
			
		||||
  onReact,
 | 
			
		||||
  onComment,
 | 
			
		||||
  onDelete,
 | 
			
		||||
  onPostDelete,
 | 
			
		||||
}) {
 | 
			
		||||
  const { reacts, title, content, user, comments, date, admin } = newsItem
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.newsRoot}>
 | 
			
		||||
      <div className={`${styles.newsContainer} ${admin && styles.adminPost}`}>
 | 
			
		||||
        <div className={styles.newsHeader}>
 | 
			
		||||
          <div
 | 
			
		||||
            className={styles.newsTitle}
 | 
			
		||||
            dangerouslySetInnerHTML={{ __html: title }}
 | 
			
		||||
          />
 | 
			
		||||
          <div className={styles.user}>
 | 
			
		||||
            <div>User #{user}</div>
 | 
			
		||||
            <div className={styles.newsDate}>{date}</div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div
 | 
			
		||||
          className={styles.newsBody}
 | 
			
		||||
          dangerouslySetInnerHTML={{ __html: content }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className={'actions'}>
 | 
			
		||||
        {uid === user ? (
 | 
			
		||||
          <span
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              onPostDelete()
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            Delete
 | 
			
		||||
          </span>
 | 
			
		||||
        ) : null}
 | 
			
		||||
        <ReactButton
 | 
			
		||||
          existingReacts={reacts}
 | 
			
		||||
          uid={uid}
 | 
			
		||||
          onClick={(reaction, isDelete) => {
 | 
			
		||||
            onReact({ type: 'news', reaction, isDelete })
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
      <Comments
 | 
			
		||||
        uid={uid}
 | 
			
		||||
        onReact={(path, reaction, isDelete) => {
 | 
			
		||||
          onReact({ type: 'comment', path, reaction, isDelete })
 | 
			
		||||
        }}
 | 
			
		||||
        onComment={onComment}
 | 
			
		||||
        onDelete={onDelete}
 | 
			
		||||
        comments={comments}
 | 
			
		||||
      />
 | 
			
		||||
      <hr />
 | 
			
		||||
      <hr />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								src/components/newsEntry.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/components/newsEntry.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
.newsBody {
 | 
			
		||||
  margin: 0px 5px;
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.newsTitle {
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
  margin: 0px 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.newsDate {
 | 
			
		||||
  margin: 0px 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.newsContainer {
 | 
			
		||||
  margin: 5px 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.adminPost {
 | 
			
		||||
  border-left: 2px solid yellow;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.newsContainer img {
 | 
			
		||||
  max-width: 100%;
 | 
			
		||||
  min-width: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.newsHeader {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.newsRoot {
 | 
			
		||||
  background-color: #191919;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								src/components/reactButton.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/components/reactButton.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
 | 
			
		||||
import Tooltip from './tooltip.js'
 | 
			
		||||
 | 
			
		||||
import styles from './reactButton.module.css'
 | 
			
		||||
import reactions from '../data/reactions.json'
 | 
			
		||||
 | 
			
		||||
function ExistingReacts({ existingReacts, onClick, uid }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.reactionContainer}>
 | 
			
		||||
      <div>React</div>
 | 
			
		||||
      {existingReacts &&
 | 
			
		||||
        Object.keys(existingReacts).map((key) => {
 | 
			
		||||
          const currReact = existingReacts[key]
 | 
			
		||||
          const react = reactions[key]
 | 
			
		||||
          if (!react) {
 | 
			
		||||
            return null
 | 
			
		||||
          }
 | 
			
		||||
          return (
 | 
			
		||||
            <div
 | 
			
		||||
              title={currReact.join(', ')}
 | 
			
		||||
              className={`${currReact.includes(uid) && styles.reacted}`}
 | 
			
		||||
              key={key}
 | 
			
		||||
              onClick={(e) => {
 | 
			
		||||
                e.stopPropagation()
 | 
			
		||||
                onClick(key, currReact.includes(uid))
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              {react.emoji} {currReact.length}
 | 
			
		||||
            </div>
 | 
			
		||||
          )
 | 
			
		||||
        })}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function RenderEmojis({ onClick }) {
 | 
			
		||||
  const [search, setSearch] = useState('')
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <input
 | 
			
		||||
        type="text"
 | 
			
		||||
        placeholder="Keresés..."
 | 
			
		||||
        onChange={(event) => {
 | 
			
		||||
          setSearch(event.target.value)
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      {Object.keys(reactions).map((key) => {
 | 
			
		||||
        const reaction = reactions[key]
 | 
			
		||||
        if (!key.includes(search.toLowerCase())) {
 | 
			
		||||
          return null
 | 
			
		||||
        }
 | 
			
		||||
        return (
 | 
			
		||||
          <div
 | 
			
		||||
            title={key}
 | 
			
		||||
            key={key}
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              onClick(key)
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            {reaction.emoji}
 | 
			
		||||
          </div>
 | 
			
		||||
        )
 | 
			
		||||
      })}
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function ReactButton({ onClick, existingReacts, uid }) {
 | 
			
		||||
  const [opened, setOpened] = useState(false)
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={styles.reactContainer}
 | 
			
		||||
      onClick={() => {
 | 
			
		||||
        setOpened(true)
 | 
			
		||||
      }}
 | 
			
		||||
      onMouseEnter={() => {}}
 | 
			
		||||
      onMouseLeave={() => {
 | 
			
		||||
        setOpened(false)
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <Tooltip
 | 
			
		||||
        opened={opened}
 | 
			
		||||
        text={() => (
 | 
			
		||||
          <ExistingReacts
 | 
			
		||||
            uid={uid}
 | 
			
		||||
            onClick={(key, isDelete) => {
 | 
			
		||||
              onClick(key, isDelete)
 | 
			
		||||
              setOpened(false)
 | 
			
		||||
            }}
 | 
			
		||||
            existingReacts={existingReacts}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
      >
 | 
			
		||||
        <div className={styles.reactionContainer}>
 | 
			
		||||
          <RenderEmojis
 | 
			
		||||
            onClick={(e) => {
 | 
			
		||||
              // setOpened(false)
 | 
			
		||||
              onClick(e)
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </Tooltip>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								src/components/reactButton.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/components/reactButton.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
.reactionContainer {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.reactionContainer > input {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background-color: var(--background-color);
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
  border: none;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  margin: 2px 5px;
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.reactionContainer > div {
 | 
			
		||||
  margin: 2px 2px;
 | 
			
		||||
  padding: 0px 10px;
 | 
			
		||||
  border: 1px solid #444;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.reactionContainer > div:hover {
 | 
			
		||||
  background-color: var(--hoover-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.break {
 | 
			
		||||
  flex-basis: 100%;
 | 
			
		||||
  height: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.reacted {
 | 
			
		||||
  color: yellow;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.reactContainer {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/components/tooltip.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/components/tooltip.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import React from 'react'
 | 
			
		||||
import styles from './tooltip.module.css'
 | 
			
		||||
 | 
			
		||||
export default function Tooltip({ children, text, opened }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.tooltip}>
 | 
			
		||||
      {text()}
 | 
			
		||||
      {opened && <span className={styles.tooltiptext}>{children}</span>}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								src/components/tooltip.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/components/tooltip.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
.tooltip {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tooltip .tooltiptext {
 | 
			
		||||
  width: 300px;
 | 
			
		||||
  max-width: 300px;
 | 
			
		||||
  height: 250px;
 | 
			
		||||
  max-height: 250px;
 | 
			
		||||
  background-color: var(--background-color);
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
  bottom: 100%;
 | 
			
		||||
  left: 0%;
 | 
			
		||||
  margin-left: -60px;
 | 
			
		||||
  transition: opacity 0.3s;
 | 
			
		||||
 | 
			
		||||
  overflow-y: scroll;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container {
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "siteUrl": "https://qmining.frylabs.net/",
 | 
			
		||||
  "apiUrl": "https://api.frylabs.net/",
 | 
			
		||||
  "apiUrl": "http://localhost:8080/",
 | 
			
		||||
  "mobileWindowWidth": 700,
 | 
			
		||||
  "maxQuestionsToRender": 250
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5417
									
								
								src/data/reactions.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5417
									
								
								src/data/reactions.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -21,11 +21,6 @@
 | 
			
		||||
  },
 | 
			
		||||
  "ranklist": {
 | 
			
		||||
    "href": "/ranklist",
 | 
			
		||||
    "text": "Ranglista"
 | 
			
		||||
  },
 | 
			
		||||
  "feedback": {
 | 
			
		||||
    "href": "/feedback",
 | 
			
		||||
    "text": "Feedback",
 | 
			
		||||
    "id": "feedback"
 | 
			
		||||
    "text": "Ranklista"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
:root {
 | 
			
		||||
  --text-color: #F2CB05;
 | 
			
		||||
  --text-color: #f2cb05;
 | 
			
		||||
  --primary-color: #9999ff;
 | 
			
		||||
  --bright-color: #f2f2f2;
 | 
			
		||||
  --background-color: #222426;
 | 
			
		||||
  --hoover-color: #191919;
 | 
			
		||||
@@ -18,7 +19,24 @@ a {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover {
 | 
			
		||||
  color: #C1C1C1;
 | 
			
		||||
  color: #c1c1c1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
textarea {
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
  background-color: var(--background-color);
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  height: 120px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  border: 1px solid #666;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input {
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
  background-color: var(--background-color);
 | 
			
		||||
  border: 1px solid #444;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.link {
 | 
			
		||||
@@ -65,7 +83,7 @@ a:hover {
 | 
			
		||||
  transition: width 0.5s, height 0.5s, ease-out 0.5s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sidebarLinks a.active{
 | 
			
		||||
.sidebarLinks a.active {
 | 
			
		||||
  border: 0.5px solid var(--text-color);
 | 
			
		||||
  color: white;
 | 
			
		||||
  text-shadow: 2px 2px 8px black;
 | 
			
		||||
@@ -288,3 +306,21 @@ select:hover {
 | 
			
		||||
  padding: 2px 6px;
 | 
			
		||||
  font-size: 13.5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.actions {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.actions > span {
 | 
			
		||||
  margin: 2px 2px;
 | 
			
		||||
  padding: 0px 10px;
 | 
			
		||||
  border: 1px solid #444;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.actions > span:hover {
 | 
			
		||||
  background-color: var(--hoover-color);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,214 +0,0 @@
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
import fetch from 'unfetch'
 | 
			
		||||
import Head from 'next/head'
 | 
			
		||||
 | 
			
		||||
import Button from '../components/Button.js'
 | 
			
		||||
 | 
			
		||||
import styles from './feedback.module.css'
 | 
			
		||||
import constants from '../constants.json'
 | 
			
		||||
 | 
			
		||||
const results = {
 | 
			
		||||
  success: 'SUCCESS',
 | 
			
		||||
  error: 'ERROR',
 | 
			
		||||
  notSent: 'NOTSENT',
 | 
			
		||||
  invalid: 'INVALID',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function Feedback() {
 | 
			
		||||
  const [form, setForm] = useState({})
 | 
			
		||||
  const [file, setFile] = useState(undefined)
 | 
			
		||||
  const [result, setResult] = useState(results.notSent)
 | 
			
		||||
  const [fileResult, setFileResult] = useState(results.notSent)
 | 
			
		||||
 | 
			
		||||
  const onChange = (event) => {
 | 
			
		||||
    setForm({
 | 
			
		||||
      ...form,
 | 
			
		||||
      [event.target.name]: event.target.value,
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderTextInputArea = (params) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={styles.inputArea}>
 | 
			
		||||
        <div className={styles.textTitle}>{params.text}</div>
 | 
			
		||||
        <textarea
 | 
			
		||||
          autoFocus={params.autoFocus}
 | 
			
		||||
          onChange={(event) => params.onChange(event)}
 | 
			
		||||
          value={form[params.name] || ''}
 | 
			
		||||
          className={styles.feedback}
 | 
			
		||||
          name={params.name}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const onFileChangeHandler = (event) => {
 | 
			
		||||
    setForm({
 | 
			
		||||
      ...form,
 | 
			
		||||
      file: event.target.files[0].name,
 | 
			
		||||
    })
 | 
			
		||||
    setFile(event.target.files[0])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderFileUploader = () => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={styles.inputArea}>
 | 
			
		||||
        <div className={styles.textTitle}>Fájl csatolása</div>
 | 
			
		||||
        <input
 | 
			
		||||
          className={styles.fileInput}
 | 
			
		||||
          type="file"
 | 
			
		||||
          name="file"
 | 
			
		||||
          onChange={onFileChangeHandler}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleSubmit = async () => {
 | 
			
		||||
    if (!form.description) {
 | 
			
		||||
      setResult(results.invalid)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const t = document.getElementById('cid').value
 | 
			
		||||
    let cid = ''
 | 
			
		||||
    let version = ''
 | 
			
		||||
    if (t) {
 | 
			
		||||
      cid = t.split('|')[0]
 | 
			
		||||
      version = t.split('|')[1]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const rawResponse = await fetch(constants.apiUrl + 'postfeedback', {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      credentials: 'include',
 | 
			
		||||
      headers: {
 | 
			
		||||
        Accept: 'application/json',
 | 
			
		||||
        'Content-Type': 'application/json',
 | 
			
		||||
      },
 | 
			
		||||
      body: JSON.stringify({
 | 
			
		||||
        ...form,
 | 
			
		||||
        cid: cid,
 | 
			
		||||
        version: version,
 | 
			
		||||
      }),
 | 
			
		||||
    })
 | 
			
		||||
    rawResponse
 | 
			
		||||
      .json()
 | 
			
		||||
      .then((resp) => {
 | 
			
		||||
        if (resp.success) {
 | 
			
		||||
          setResult(results.success)
 | 
			
		||||
        } else {
 | 
			
		||||
          setResult(results.error)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .catch((err) => {
 | 
			
		||||
        setResult(results.error)
 | 
			
		||||
        console.error(err)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    if (file) {
 | 
			
		||||
      const formData = new FormData() // eslint-disable-line
 | 
			
		||||
      formData.append('file', file)
 | 
			
		||||
 | 
			
		||||
      const rawFileResponse = await fetch(
 | 
			
		||||
        constants.apiUrl + 'postfeedbackfile',
 | 
			
		||||
        {
 | 
			
		||||
          method: 'POST',
 | 
			
		||||
          credentials: 'include',
 | 
			
		||||
          headers: {
 | 
			
		||||
            Accept: 'application/json',
 | 
			
		||||
          },
 | 
			
		||||
          body: formData,
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
      rawFileResponse
 | 
			
		||||
        .json()
 | 
			
		||||
        .then((resp) => {
 | 
			
		||||
          if (resp.success) {
 | 
			
		||||
            setFileResult(results.success)
 | 
			
		||||
          } else {
 | 
			
		||||
            setFileResult(results.error)
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        .catch((err) => {
 | 
			
		||||
          setFileResult(results.error)
 | 
			
		||||
          console.error('FILE error', err)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderResult = () => {
 | 
			
		||||
    if (results === result.success) {
 | 
			
		||||
      return <div>sucess</div>
 | 
			
		||||
    } else if (results === result.error) {
 | 
			
		||||
      return <div>error</div>
 | 
			
		||||
    } else if (results === result.invalid) {
 | 
			
		||||
      return <div>invalid</div>
 | 
			
		||||
    } else {
 | 
			
		||||
      return null
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // action={constants.apiUrl + 'badtestsender'} encType='multipart/form-data' method='post'
 | 
			
		||||
  const renderForm = (props) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={styles.feedback}>
 | 
			
		||||
        {props.noDesc ? (
 | 
			
		||||
          <div className={styles.errorMsg}>Mező kitöltése kötelező!</div>
 | 
			
		||||
        ) : null}
 | 
			
		||||
        {renderTextInputArea({
 | 
			
		||||
          text: 'Rövid leírás',
 | 
			
		||||
          name: 'description',
 | 
			
		||||
          onChange: onChange,
 | 
			
		||||
          autoFocus: true,
 | 
			
		||||
        })}
 | 
			
		||||
        <div className={styles.desc}>
 | 
			
		||||
          Bal aluli levelesláda ikonnál keresd majd a választ
 | 
			
		||||
        </div>
 | 
			
		||||
        {renderFileUploader()}
 | 
			
		||||
        <div className={styles.buttonContainer}>
 | 
			
		||||
          <button className={styles.button} onClick={handleSubmit}>
 | 
			
		||||
            Küldés
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <input type="text" id="cid" name="cid" hidden />
 | 
			
		||||
        {renderResult()}
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderStuff = () => {
 | 
			
		||||
    if (result === results.notSent && fileResult === results.notSent) {
 | 
			
		||||
      return <div className={styles.textTitle}>{renderForm({})}</div>
 | 
			
		||||
    } else if (result === results.invalid) {
 | 
			
		||||
      return (
 | 
			
		||||
        <div className={styles.textTitle}>{renderForm({ noDesc: true })}</div>
 | 
			
		||||
      )
 | 
			
		||||
    } else if (result === results.success && !file) {
 | 
			
		||||
      return <div className={styles.textTitle}>Visszajelzés elküldve c:</div>
 | 
			
		||||
    } else if (result === results.error && fileResult === results.success) {
 | 
			
		||||
      return <div className={styles.textTitle}>Hiba küldés közben :c</div>
 | 
			
		||||
    } else if (result === results.success && fileResult === results.error) {
 | 
			
		||||
      return (
 | 
			
		||||
        <div className={styles.textTitle}>
 | 
			
		||||
          Visszajelzés elküldve, de fájlt nem sikerült elküldeni :c
 | 
			
		||||
        </div>
 | 
			
		||||
      )
 | 
			
		||||
    } else if (result === results.success && fileResult === results.success) {
 | 
			
		||||
      return <div className={styles.textTitle}>Visszajelzés elküldve c:</div>
 | 
			
		||||
    } else {
 | 
			
		||||
      return <div className={styles.textTitle}>Bit of a fuckup here</div>
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <Head>
 | 
			
		||||
        <title>Feedback - Qmining | Frylabs.net</title>
 | 
			
		||||
      </Head>
 | 
			
		||||
      <Button text="IRC chat" href="/irc" />
 | 
			
		||||
      <p />
 | 
			
		||||
      <hr />
 | 
			
		||||
      <p />
 | 
			
		||||
      {renderStuff()}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
.feedback {
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
  background-color: var(--background-color);
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  height: 120px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.buttonContainer {
 | 
			
		||||
  text-align: 'center';
 | 
			
		||||
  width: 200px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.desc {
 | 
			
		||||
  font-size: 16px;
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.textTitle {
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button {
 | 
			
		||||
  background-color: var(--text-color);
 | 
			
		||||
  border: none;
 | 
			
		||||
  padding: 10px 30px;
 | 
			
		||||
  color: white;
 | 
			
		||||
  width: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.textInputArea {
 | 
			
		||||
  padding: 20px 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fileInput {
 | 
			
		||||
  margin: 10px;
 | 
			
		||||
  color: var(--text-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.errorMsg {
 | 
			
		||||
  color: red;
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,8 @@ import Link from 'next/link'
 | 
			
		||||
 | 
			
		||||
import LoadingIndicator from '../components/LoadingIndicator'
 | 
			
		||||
import Sleep from '../components/sleep'
 | 
			
		||||
import NewsEntry from '../components/newsEntry'
 | 
			
		||||
import Composer from '../components/composer'
 | 
			
		||||
import DbSelector from '../components/dbSelector.js'
 | 
			
		||||
 | 
			
		||||
import styles from './index.module.css'
 | 
			
		||||
@@ -21,83 +23,254 @@ const links = {
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function Index(props) {
 | 
			
		||||
  const [news, setNews] = useState(null)
 | 
			
		||||
  const [allQrSelector, setAllQrSelector] = useState(null)
 | 
			
		||||
  const motd = props.globalData.motd
 | 
			
		||||
  // const userSpecificMotd = props.globalData.userSpecificMotd
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    console.info('Fetching news.json')
 | 
			
		||||
function fetchNews() {
 | 
			
		||||
  return new Promise((resolve) => {
 | 
			
		||||
    fetch(`${constants.apiUrl}news.json`, {
 | 
			
		||||
      credentials: 'include',
 | 
			
		||||
    })
 | 
			
		||||
      .then((resp) => {
 | 
			
		||||
        return resp.json()
 | 
			
		||||
      })
 | 
			
		||||
      .then((data) => {
 | 
			
		||||
        setNews(data)
 | 
			
		||||
      .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({
 | 
			
		||||
        title: title,
 | 
			
		||||
        content: content,
 | 
			
		||||
      }),
 | 
			
		||||
    })
 | 
			
		||||
      .then((res) => {
 | 
			
		||||
        return res.json()
 | 
			
		||||
      })
 | 
			
		||||
      .then((res) => {
 | 
			
		||||
        resolve(res)
 | 
			
		||||
      })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function postFeedback(content, file) {
 | 
			
		||||
  return new Promise((resolve) => {
 | 
			
		||||
    const promises = [
 | 
			
		||||
      fetch(constants.apiUrl + 'postfeedback', {
 | 
			
		||||
        method: 'POST',
 | 
			
		||||
        credentials: 'include',
 | 
			
		||||
        headers: {
 | 
			
		||||
          Accept: 'application/json',
 | 
			
		||||
          'Content-Type': 'application/json',
 | 
			
		||||
        },
 | 
			
		||||
        body: JSON.stringify({
 | 
			
		||||
          content: content,
 | 
			
		||||
        }),
 | 
			
		||||
      }).then((res) => {
 | 
			
		||||
        return res.json()
 | 
			
		||||
      }),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    if (file) {
 | 
			
		||||
      console.log('FIEEEEEEEEEELE')
 | 
			
		||||
      const formData = new FormData() // eslint-disable-line
 | 
			
		||||
      formData.append('file', file)
 | 
			
		||||
 | 
			
		||||
      promises.push(
 | 
			
		||||
        fetch(constants.apiUrl + 'postfeedbackfile', {
 | 
			
		||||
          method: 'POST',
 | 
			
		||||
          credentials: 'include',
 | 
			
		||||
          headers: {
 | 
			
		||||
            Accept: 'application/json',
 | 
			
		||||
          },
 | 
			
		||||
          body: formData,
 | 
			
		||||
        }).then((res) => {
 | 
			
		||||
          return res.json()
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Promise.all(promises).then((res) => {
 | 
			
		||||
      resolve(res)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function Index({ globalData }) {
 | 
			
		||||
  const userId = globalData.userId
 | 
			
		||||
  const motd = globalData.motd
 | 
			
		||||
  const [news, setNews] = useState(null)
 | 
			
		||||
  const [allQrSelector, setAllQrSelector] = useState(null)
 | 
			
		||||
  // const userSpecificMotd = props.globalData.userSpecificMotd
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    console.info('Fetching news.json')
 | 
			
		||||
    fetchNews().then((res) => {
 | 
			
		||||
      setNews(res)
 | 
			
		||||
    })
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  const renderQAItem = (newsItem, key) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div key={key} className={styles.itemContainer}>
 | 
			
		||||
        <div className={styles.itemNumber}>{key} :</div>
 | 
			
		||||
        <div
 | 
			
		||||
          className={styles.question}
 | 
			
		||||
          dangerouslySetInnerHTML={{ __html: newsItem.q }}
 | 
			
		||||
        />
 | 
			
		||||
        <div
 | 
			
		||||
          className={styles.answer}
 | 
			
		||||
          dangerouslySetInnerHTML={{ __html: newsItem.a }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderNewsItem = (newsItem, key) => {
 | 
			
		||||
    return (
 | 
			
		||||
      <div key={key} className={styles.itemContainer}>
 | 
			
		||||
        <div className={styles.itemNumber}>{key} :</div>
 | 
			
		||||
        <div
 | 
			
		||||
          className={styles.newsTitle}
 | 
			
		||||
          dangerouslySetInnerHTML={{ __html: newsItem.title }}
 | 
			
		||||
        />
 | 
			
		||||
        <div
 | 
			
		||||
          className={styles.newsBody}
 | 
			
		||||
          dangerouslySetInnerHTML={{ __html: newsItem.body }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const renderNews = () => {
 | 
			
		||||
    if (news) {
 | 
			
		||||
      let questions = Object.keys(news)
 | 
			
		||||
      let newsItems = Object.keys(news)
 | 
			
		||||
        .map((key) => {
 | 
			
		||||
          let newsItem = news[key]
 | 
			
		||||
          if (newsItem.q) {
 | 
			
		||||
            return (
 | 
			
		||||
              <div key={key}>
 | 
			
		||||
                {renderQAItem(newsItem, key)}
 | 
			
		||||
              </div>
 | 
			
		||||
            )
 | 
			
		||||
          } else {
 | 
			
		||||
            return (
 | 
			
		||||
              <div key={key}>
 | 
			
		||||
                {renderNewsItem(newsItem, key)}
 | 
			
		||||
              </div>
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
          let newsEntryData = news[key]
 | 
			
		||||
          return (
 | 
			
		||||
            <NewsEntry
 | 
			
		||||
              onPostDelete={() => {
 | 
			
		||||
                fetch(constants.apiUrl + 'rmPost', {
 | 
			
		||||
                  method: 'POST',
 | 
			
		||||
                  credentials: 'include',
 | 
			
		||||
                  headers: {
 | 
			
		||||
                    Accept: 'application/json',
 | 
			
		||||
                    'Content-Type': 'application/json',
 | 
			
		||||
                  },
 | 
			
		||||
                  body: JSON.stringify({
 | 
			
		||||
                    newsKey: key,
 | 
			
		||||
                  }),
 | 
			
		||||
                })
 | 
			
		||||
                  .then((res) => {
 | 
			
		||||
                    return res.json()
 | 
			
		||||
                  })
 | 
			
		||||
                  .then((res) => {
 | 
			
		||||
                    setNews(res.news)
 | 
			
		||||
                  })
 | 
			
		||||
              }}
 | 
			
		||||
              onReact={({ type, path, reaction, isDelete }) => {
 | 
			
		||||
                if (type === 'news') {
 | 
			
		||||
                  fetch(constants.apiUrl + 'infos', {
 | 
			
		||||
                    method: 'POST',
 | 
			
		||||
                    credentials: 'include',
 | 
			
		||||
                    headers: {
 | 
			
		||||
                      Accept: 'application/json',
 | 
			
		||||
                      'Content-Type': 'application/json',
 | 
			
		||||
                    },
 | 
			
		||||
                    body: JSON.stringify({
 | 
			
		||||
                      react: reaction,
 | 
			
		||||
                      newsKey: key,
 | 
			
		||||
                      isDelete: isDelete,
 | 
			
		||||
                    }),
 | 
			
		||||
                  })
 | 
			
		||||
                    .then((res) => {
 | 
			
		||||
                      return res.json()
 | 
			
		||||
                    })
 | 
			
		||||
                    .then((res) => {
 | 
			
		||||
                      setNews(res.news)
 | 
			
		||||
                    })
 | 
			
		||||
                } else if (type === 'comment') {
 | 
			
		||||
                  fetch(constants.apiUrl + 'comment', {
 | 
			
		||||
                    method: 'POST',
 | 
			
		||||
                    credentials: 'include',
 | 
			
		||||
                    headers: {
 | 
			
		||||
                      Accept: 'application/json',
 | 
			
		||||
                      'Content-Type': 'application/json',
 | 
			
		||||
                    },
 | 
			
		||||
                    body: JSON.stringify({
 | 
			
		||||
                      type: 'reaction',
 | 
			
		||||
                      newsKey: key,
 | 
			
		||||
                      path: path,
 | 
			
		||||
                      reaction: reaction,
 | 
			
		||||
                      isDelete: isDelete,
 | 
			
		||||
                    }),
 | 
			
		||||
                  })
 | 
			
		||||
                    .then((res) => {
 | 
			
		||||
                      return res.json()
 | 
			
		||||
                    })
 | 
			
		||||
                    .then((res) => {
 | 
			
		||||
                      setNews(res.news)
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
              }}
 | 
			
		||||
              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,
 | 
			
		||||
                    newsKey: key,
 | 
			
		||||
                  }),
 | 
			
		||||
                })
 | 
			
		||||
                  .then((res) => {
 | 
			
		||||
                    return res.json()
 | 
			
		||||
                  })
 | 
			
		||||
                  .then((res) => {
 | 
			
		||||
                    setNews(res.news)
 | 
			
		||||
                  })
 | 
			
		||||
              }}
 | 
			
		||||
              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,
 | 
			
		||||
                    newsKey: key,
 | 
			
		||||
                  }),
 | 
			
		||||
                })
 | 
			
		||||
                  .then((res) => {
 | 
			
		||||
                    return res.json()
 | 
			
		||||
                  })
 | 
			
		||||
                  .then((res) => {
 | 
			
		||||
                    setNews(res.news)
 | 
			
		||||
                  })
 | 
			
		||||
              }}
 | 
			
		||||
              uid={userId}
 | 
			
		||||
              key={key}
 | 
			
		||||
              newsKey={key}
 | 
			
		||||
              newsItem={newsEntryData}
 | 
			
		||||
            />
 | 
			
		||||
          )
 | 
			
		||||
        })
 | 
			
		||||
        .reverse()
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <div>
 | 
			
		||||
          <div className={styles.title}>Hírek</div>
 | 
			
		||||
            <hr />
 | 
			
		||||
          <div className={styles.questionscontainer}>{questions}</div>
 | 
			
		||||
          <hr />
 | 
			
		||||
          <div className={styles.title}>Forum</div>
 | 
			
		||||
          <hr />
 | 
			
		||||
          <Composer
 | 
			
		||||
            onSubmit={(type, title, content, file) => {
 | 
			
		||||
              if (!content) {
 | 
			
		||||
                alert('Üres a tartalom!')
 | 
			
		||||
                return
 | 
			
		||||
              }
 | 
			
		||||
              console.log(type, title, content, file)
 | 
			
		||||
              if (type === 'private') {
 | 
			
		||||
                postFeedback(content, file).then((res) => {
 | 
			
		||||
                  console.log(res)
 | 
			
		||||
                  alert('Privát visszajelzés elküldve!')
 | 
			
		||||
                })
 | 
			
		||||
              } else {
 | 
			
		||||
                if (!title) {
 | 
			
		||||
                  alert('Üres a téma!')
 | 
			
		||||
                  return
 | 
			
		||||
                }
 | 
			
		||||
                addPost(title, content).then((res) => {
 | 
			
		||||
                  setNews(res.news)
 | 
			
		||||
                })
 | 
			
		||||
              }
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
          <hr />
 | 
			
		||||
          <div>{newsItems}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      )
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -109,6 +282,7 @@ export default function Index(props) {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={styles.motd_body}>
 | 
			
		||||
        <div className={styles.title}>MOTD</div>
 | 
			
		||||
        <hr />
 | 
			
		||||
        {motd ? (
 | 
			
		||||
          <div
 | 
			
		||||
            className={styles.motd}
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clickable:hover {
 | 
			
		||||
  background-color: #666666;
 | 
			
		||||
  background-color: var(--hoover-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clickable {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user