recursion
This commit is contained in:
42
src/App.js
42
src/App.js
@@ -1,18 +1,16 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import PostCommentList from './components/PostCommentList';
|
||||
import NewPostForm from './components/NewPostForm';
|
||||
import './App.scss';
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faTimes} from "@fortawesome/free-solid-svg-icons";
|
||||
import {postApi} from "./api/posts.api";
|
||||
import {locationUtils} from "./utils/location";
|
||||
import PostComment from "./components/PostComment";
|
||||
import Post from "./components/Post";
|
||||
import keycloak from "./Authentification/Keycloak";
|
||||
|
||||
const App = () => {
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [comments, setComments] = useState([]);
|
||||
const [selectedPost, setSelectedPost] = useState(null);
|
||||
const [selectedPost, selectPost] = useState(null);
|
||||
const [comment, setComment] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
@@ -23,15 +21,10 @@ const App = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if(selectedPost) {
|
||||
setSelectedPost(posts.find(post => post.id === selectedPost.id));
|
||||
selectPost(posts.find(post => post.id === selectedPost.id));
|
||||
}
|
||||
}, [posts]);
|
||||
|
||||
useEffect(() => {
|
||||
if(selectedPost) {
|
||||
setComments(selectedPost.comments);
|
||||
}
|
||||
}, [selectedPost]);
|
||||
|
||||
const reload = async () => {
|
||||
await loadPosts().then(data => {
|
||||
@@ -53,44 +46,28 @@ const App = () => {
|
||||
await postApi.createNewPost("", commentText, postId).then(await reload);
|
||||
};
|
||||
|
||||
const vote = (postId, reaction) => {
|
||||
// TODO postApi.reactToPost(postId)
|
||||
console.log(postId, reaction);
|
||||
};
|
||||
|
||||
const handlePostClick = (post) => {
|
||||
setSelectedPost(post);
|
||||
};
|
||||
|
||||
const handleCommentSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (comment.trim() && selectedPost) {
|
||||
await addComment(selectedPost.id, comment);
|
||||
setComment('');
|
||||
await reload();
|
||||
handlePostClick(selectedPost);
|
||||
selectPost(selectedPost);
|
||||
}
|
||||
};
|
||||
|
||||
const closePost = () => {
|
||||
setSelectedPost(null);
|
||||
setComment('');
|
||||
selectPost(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<button onClick={() => keycloak.logout()}>Logout</button>
|
||||
<button className="logout" onClick={() => keycloak.logout()}>Logout</button>
|
||||
<h1>SWA - Jodel</h1>
|
||||
{!selectedPost && (
|
||||
<div>
|
||||
<NewPostForm addPost={addPost}/>
|
||||
<PostCommentList
|
||||
posts={posts}
|
||||
addComment={addComment}
|
||||
vote={vote}
|
||||
onPostClick={handlePostClick}
|
||||
type={"posts"}
|
||||
/>
|
||||
{posts.map(post => <Post key={post.id} post={post} recursionDepth={5} selectPost={selectPost} reload={reload} />)}
|
||||
</div>
|
||||
)}
|
||||
{selectedPost && (
|
||||
@@ -98,7 +75,7 @@ const App = () => {
|
||||
<div className="close-post">
|
||||
<FontAwesomeIcon className={"close-button"} onClick={closePost} icon={faTimes}/>
|
||||
</div>
|
||||
<PostComment post={selectedPost} vote={vote} type={"post"}></PostComment>
|
||||
<Post key={selectedPost.id} post={selectedPost} recursionDepth={0} selectPost={null} reload={reload}/>
|
||||
<form className="comment-form" onSubmit={handleCommentSubmit}>
|
||||
<div className="textarea-container">
|
||||
<textarea
|
||||
@@ -117,8 +94,7 @@ const App = () => {
|
||||
<button type="submit">Kommentieren</button>
|
||||
</form>
|
||||
<div className="comments">
|
||||
{comments && <PostCommentList posts={comments} onPostClick={null} vote={vote}
|
||||
type={"comments"}></PostCommentList>}
|
||||
{selectedPost.comments.map(post => <Post key={selectedPost.id} post={post} recursionDepth={0} selectPost={null} reload={reload} />)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
30
src/App.scss
30
src/App.scss
@@ -241,3 +241,33 @@ button {
|
||||
background-color: #e68a00;
|
||||
}
|
||||
}
|
||||
|
||||
.comment {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.post-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nrOfComments {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-self: flex-end;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
font-size: 1.5em;
|
||||
padding: 0.35em;
|
||||
* {
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #ff9908;
|
||||
}
|
||||
|
||||
.logout {
|
||||
margin-left: 75%;
|
||||
width: 25%
|
||||
}
|
||||
@@ -2,22 +2,22 @@ import axios from "axios"
|
||||
import {locationUtils} from "../utils/location";
|
||||
import keycloak from "../Authentification/Keycloak";
|
||||
|
||||
const path = "https://api.jodel.anxietyprime.de/post";
|
||||
const path = "https://api.jodel.anxietyprime.de";
|
||||
|
||||
export const postApi = {
|
||||
async getPostsByCoords (lon, lat) {
|
||||
if (!keycloak.authenticated) return [];
|
||||
return (await axios.get(`${path}s/${lon}/${lat}`)).data;
|
||||
return (await axios.get(`${path}/posts/${lon}/${lat}`)).data;
|
||||
},
|
||||
async getPostByID (id) {
|
||||
if (!keycloak.authenticated) return [];
|
||||
return (await axios.get(`${path}/${id}`)).data;
|
||||
return (await axios.get(`${path}/post/${id}`)).data;
|
||||
},
|
||||
async createNewPost(title, content, parent = null) {
|
||||
const location = await locationUtils.getCurrentLocation();
|
||||
if(location.lon && location.lat) {
|
||||
if(parent !== null) {
|
||||
await axios.post(`${path}s`, {
|
||||
await axios.post(`${path}/posts`, {
|
||||
"authorID": 1,
|
||||
"title": title,
|
||||
"content": content,
|
||||
@@ -29,7 +29,7 @@ export const postApi = {
|
||||
"parent": parent
|
||||
});
|
||||
} else {
|
||||
await axios.post(`${path}s`, {
|
||||
await axios.post(`${path}/posts`, {
|
||||
"authorID": 1,
|
||||
"title": title,
|
||||
"content": content,
|
||||
@@ -45,8 +45,6 @@ export const postApi = {
|
||||
}
|
||||
},
|
||||
async reactToPost(id, reaction) {
|
||||
await axios.put(`${path}/${id}`, {
|
||||
"reaction": reaction,
|
||||
})
|
||||
await axios.patch(`${path}/post/${id}`, {reaction: reaction});
|
||||
}
|
||||
}
|
||||
86
src/components/Post.js
Normal file
86
src/components/Post.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faCaretUp,
|
||||
faCaretDown,
|
||||
faCommentAlt,
|
||||
faComments,
|
||||
faAngleUp,
|
||||
faAngleDown
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faMessage} from "@fortawesome/free-solid-svg-icons/faMessage";
|
||||
import {postApi} from "../api/posts.api";
|
||||
|
||||
const Post = ({ post, recursionDepth, selectPost, reload }) => {
|
||||
|
||||
const [showComments, setShowComments] = React.useState(false);
|
||||
|
||||
const upvote = () => {
|
||||
if (post.reaction === true) {
|
||||
revokeVote();
|
||||
return;
|
||||
}
|
||||
postApi.reactToPost(post.id, true).then(reload);
|
||||
}
|
||||
const downVote = () => {
|
||||
if (post.reaction === false) {
|
||||
revokeVote();
|
||||
return;
|
||||
}
|
||||
postApi.reactToPost(post.id, false).then(reload);
|
||||
}
|
||||
const revokeVote = () => {
|
||||
postApi.reactToPost(post.id, null).then(reload);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="post" onClick={() => setShowComments(!showComments)}>
|
||||
<div className="post-container">
|
||||
{post.id === post.parent && <div className="post-title">
|
||||
<h3>{post.title}</h3>
|
||||
</div>}
|
||||
<div className="post-content">
|
||||
<p>{post.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="nrOfComments">
|
||||
<FontAwesomeIcon className="icon" icon={faComments}/>
|
||||
<span className="">{post.comments.length}</span>
|
||||
</div>
|
||||
<div className="post-right">
|
||||
<div className="post-date">
|
||||
{post.date.slice(8, 10) + "." + post.date.slice(5, 7) + "." + post.date.slice(0, 4)}
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="votes">
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
upvote();
|
||||
}}>
|
||||
{post.reaction === true ? <FontAwesomeIcon icon={faCaretUp}/> : <FontAwesomeIcon icon={faAngleUp} />}
|
||||
</button>
|
||||
<span>{post.reactions.positive - post.reactions.negative}</span>
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
downVote();
|
||||
}}>
|
||||
{post.reaction === false ? <FontAwesomeIcon icon={faCaretDown}/> : <FontAwesomeIcon icon={faAngleDown} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{recursionDepth !== 0 && <button onClick={() => selectPost(post)}>Antworten</button>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="comment">
|
||||
{recursionDepth !== 0 && showComments && post.comments.map(post => <Post key={post.id} post={post}
|
||||
recursionDepth={recursionDepth - 1}
|
||||
selectPost={selectPost}
|
||||
reload={reload} />)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
;
|
||||
};
|
||||
|
||||
export default Post;
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faCaretUp, faCaretDown } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const PostComment = ({ post, vote, type }) => {
|
||||
return (
|
||||
<div className="post">
|
||||
<div className="post-container">
|
||||
{type === "post" && <div className="post-title">
|
||||
<h3>{post.title}</h3>
|
||||
</div>}
|
||||
<div className="post-content">
|
||||
<p>{post.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="post-right">
|
||||
<div className="post-date">
|
||||
{post.date.slice(8, 10) + "." + post.date.slice(5, 7) + "." + post.date.slice(0, 4)}
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="votes">
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
vote(post.id, post.reaction !== null ? null : true);
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faCaretUp}/>
|
||||
</button>
|
||||
<span>{post.reactions.positive - post.reactions.negative}</span>
|
||||
<button onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
vote(post.id, post.reaction !== null ? null : false);
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faCaretDown}/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostComment;
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
import PostComment from './PostComment';
|
||||
|
||||
const PostCommentList = ({ posts, vote, onPostClick, type }) => {
|
||||
return (
|
||||
<div className="post-list">
|
||||
<h3>{type === "posts" ? "Posts" : "Kommentare"}</h3>
|
||||
{type === "posts" && posts.map(post => (
|
||||
post.id === post.parent && (
|
||||
<div key={post.id} onClick={() => onPostClick(post)}>
|
||||
<PostComment
|
||||
post={post}
|
||||
vote={vote}
|
||||
type={"post"}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
))}
|
||||
{type === "comments" && posts.map(comment => (
|
||||
<div key={comment.id}>
|
||||
<PostComment
|
||||
post={comment}
|
||||
vote={vote}
|
||||
type={"comment"}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostCommentList;
|
||||
Reference in New Issue
Block a user