restructured code (modal => internal view)
This commit is contained in:
95
src/App.js
95
src/App.js
@@ -1,12 +1,12 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import PostList from './components/PostList';
|
import PostList from './components/PostList';
|
||||||
import NewPostForm from './components/NewPostForm';
|
import NewPostForm from './components/NewPostForm';
|
||||||
import Modal from './components/Modal';
|
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faCaretDown, faCaretUp} from "@fortawesome/free-solid-svg-icons";
|
import {faCaretDown, faCaretUp, faTimes} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {postApi} from "./api/posts.api";
|
import {postApi} from "./api/posts.api";
|
||||||
import {locationUtils} from "./utils/location";
|
import {locationUtils} from "./utils/location";
|
||||||
|
import Post from "./components/Post";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [posts, setPosts] = useState([]);
|
const [posts, setPosts] = useState([]);
|
||||||
@@ -15,7 +15,9 @@ const App = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(posts.length === 0) {
|
if(posts.length === 0) {
|
||||||
loadPosts().then(data => setPosts(data))
|
loadPosts().then(data => setPosts(data));
|
||||||
|
} else {
|
||||||
|
console.log(posts);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -24,50 +26,17 @@ const App = () => {
|
|||||||
return await postApi.getPostsByCoords(location.lon, location.lat);
|
return await postApi.getPostsByCoords(location.lon, location.lat);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addPost = (text) => {
|
const addPost = async (title, text) => {
|
||||||
const newPost = { id: Date.now(), text, comments: [], upvotes: 0, downvotes: 0 };
|
await postApi.createNewPost(title, text);
|
||||||
setPosts([newPost, ...posts]);
|
loadPosts().then(data => setPosts(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
const addComment = (postId, commentText) => {
|
const addComment = (postId, commentText) => {
|
||||||
const newComment = { id: Date.now(), text: commentText, upvotes: 0, downvotes: 0 };
|
|
||||||
setPosts(posts.map(post =>
|
|
||||||
post.id === postId ? { ...post, comments: [...post.comments, newComment] } : post
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const upvotePost = (postId) => {
|
const vote = (postId, reaction) => {
|
||||||
setPosts(posts.map(post =>
|
// TODO postApi.reactToPost(postId)
|
||||||
post.id === postId ? { ...post, upvotes: post.upvotes + 1 } : post
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const downvotePost = (postId) => {
|
|
||||||
setPosts(posts.map(post =>
|
|
||||||
post.id === postId ? { ...post, downvotes: post.downvotes + 1 } : post
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const upvoteComment = (postId, commentId) => {
|
|
||||||
setPosts(posts.map(post =>
|
|
||||||
post.id === postId ? {
|
|
||||||
...post,
|
|
||||||
comments: post.comments.map(comment =>
|
|
||||||
comment.id === commentId ? { ...comment, upvotes: comment.upvotes + 1 } : comment
|
|
||||||
)
|
|
||||||
} : post
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const downvoteComment = (postId, commentId) => {
|
|
||||||
setPosts(posts.map(post =>
|
|
||||||
post.id === postId ? {
|
|
||||||
...post,
|
|
||||||
comments: post.comments.map(comment =>
|
|
||||||
comment.id === commentId ? { ...comment, downvotes: comment.downvotes + 1 } : comment
|
|
||||||
)
|
|
||||||
} : post
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePostClick = (post) => {
|
const handlePostClick = (post) => {
|
||||||
@@ -82,35 +51,46 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closePost = () => {
|
||||||
setSelectedPost(null);
|
setSelectedPost(null);
|
||||||
setComment('');
|
setComment('');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
|
{!selectedPost && (
|
||||||
|
<div>
|
||||||
<h1>SWA - Jodel</h1>
|
<h1>SWA - Jodel</h1>
|
||||||
<NewPostForm addPost={addPost} />
|
<NewPostForm addPost={addPost}/>
|
||||||
<PostList
|
<PostList
|
||||||
posts={posts}
|
posts={posts}
|
||||||
addComment={addComment}
|
addComment={addComment}
|
||||||
upvotePost={upvotePost}
|
vote={vote}
|
||||||
downvotePost={downvotePost}
|
|
||||||
upvoteComment={upvoteComment}
|
|
||||||
downvoteComment={downvoteComment}
|
|
||||||
onPostClick={handlePostClick}
|
onPostClick={handlePostClick}
|
||||||
/>
|
/>
|
||||||
<Modal isOpen={!!selectedPost} onClose={closeModal}>
|
</div>
|
||||||
|
)}
|
||||||
{selectedPost && (
|
{selectedPost && (
|
||||||
<div>
|
<div className="single-post-view">
|
||||||
<p>{selectedPost.text}</p>
|
<div className="close-post">
|
||||||
|
<FontAwesomeIcon className={"close-button"} onClick={closePost} icon={faTimes}/>
|
||||||
|
</div>
|
||||||
|
<Post post={selectedPost} vote={vote}></Post>
|
||||||
<form className="comment-form" onSubmit={handleCommentSubmit}>
|
<form className="comment-form" onSubmit={handleCommentSubmit}>
|
||||||
<input
|
<div className="textarea-container">
|
||||||
type="text"
|
<textarea
|
||||||
|
rows={15}
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={(e) => setComment(e.target.value)}
|
onChange={(e) => setComment(e.target.value)}
|
||||||
placeholder="Kommentieren..."
|
placeholder="Kommentieren..."
|
||||||
|
maxLength={500}
|
||||||
/>
|
/>
|
||||||
|
<div className="char-count-container">
|
||||||
|
<span className={comment.length === 500 ? "char-count max-char-count" : "char-count"}>
|
||||||
|
{comment.length} / 500
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="submit">Kommentieren</button>
|
<button type="submit">Kommentieren</button>
|
||||||
</form>
|
</form>
|
||||||
<div className="comments">
|
<div className="comments">
|
||||||
@@ -118,12 +98,12 @@ const App = () => {
|
|||||||
<div key={comment.id} className="comment">
|
<div key={comment.id} className="comment">
|
||||||
<p>{comment.text}</p>
|
<p>{comment.text}</p>
|
||||||
<div className="votes">
|
<div className="votes">
|
||||||
<button onClick={() => upvoteComment(selectedPost.id, comment.id)}>
|
<button onClick={() => vote(selectedPost.id, comment.id)}>
|
||||||
<FontAwesomeIcon icon={faCaretUp} />
|
<FontAwesomeIcon icon={faCaretUp}/>
|
||||||
</button>
|
</button>
|
||||||
<span>{comment.upvotes - comment.downvotes}</span>
|
<span>{comment.upvotes - comment.downvotes}</span>
|
||||||
<button onClick={() => downvoteComment(selectedPost.id, comment.id)}>
|
<button onClick={() => vote(selectedPost.id, comment.id)}>
|
||||||
<FontAwesomeIcon icon={faCaretDown} />
|
<FontAwesomeIcon icon={faCaretDown}/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +111,6 @@ const App = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
112
src/App.scss
112
src/App.scss
@@ -30,49 +30,6 @@ h3 {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 1em;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
|
|
||||||
&.single-row {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.char-count-container {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 8px;
|
|
||||||
right: 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: grey;
|
|
||||||
background: white;
|
|
||||||
padding: 2px 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-char-count {
|
|
||||||
background-color: rgba(255, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
background-color: #ff9908;
|
background-color: #ff9908;
|
||||||
@@ -88,8 +45,7 @@ h3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-list {
|
.post {
|
||||||
.post {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -195,5 +151,71 @@ h3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-post-view {
|
||||||
|
.close-post {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
font-size: 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #ff9908;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
|
||||||
|
&.single-row {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.char-count-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: grey;
|
||||||
|
background: white;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-char-count {
|
||||||
|
background-color: rgba(255, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #ff9908;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e68a00;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import './Modal.scss';
|
|
||||||
|
|
||||||
const Modal = ({ isOpen, onClose, children }) => {
|
|
||||||
if (!isOpen) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="modal-overlay">
|
|
||||||
<div className="modal">
|
|
||||||
<button className="close-button" onClick={onClose}>
|
|
||||||
<FontAwesomeIcon icon={faTimes} />
|
|
||||||
</button>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Modal;
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 600px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5em;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #ff9908;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user