Compare commits

..

36 Commits

Author SHA1 Message Date
8968e990ec fixed comment button moving during further comments 2024-06-19 13:35:54 +02:00
94996fb45c pinned answer Buttons to bottom of post 2024-06-19 13:01:11 +02:00
29af08f79d moved reaction buttons a little up 2024-06-19 12:03:35 +02:00
a6ef1c71e2 added time to post 2024-06-19 11:57:46 +02:00
2f4eb05233 fixed post sorting for chrome and opera 2024-06-19 09:49:50 +02:00
34e44d2310 fix to not being able to reply to comments 2024-06-18 12:27:34 +02:00
f2f096ef67 changed displayed username 2024-06-15 13:09:22 +02:00
Pete-Gerlach
3f13bbdd19 show anonymousID in Posts and Comments 2024-06-14 16:27:02 +02:00
1ca4235283 added linebreak in posts 2024-06-14 09:44:24 +02:00
96753a774c added dev env 2024-06-13 23:40:09 +02:00
625485d318 removed logging 2024-06-13 23:13:20 +02:00
eeae861353 typo 2024-06-13 23:09:54 +02:00
d524a831cd test 2024-06-13 23:07:58 +02:00
1f947e3200 removed unnecessary entrypoint code 2024-06-13 22:50:43 +02:00
f92b7345ae removed unnecessary entrypoint code 2024-06-13 22:49:06 +02:00
6f9933f332 removed unnecessary entrypoint code 2024-06-13 22:48:55 +02:00
3936d1c651 removed unnecessary entrypoint code 2024-06-13 22:42:04 +02:00
e173888692 more tests 2024-06-13 22:36:57 +02:00
621e414f95 more tests 2024-06-13 22:28:01 +02:00
454973b017 test 2024-06-13 22:19:23 +02:00
17a29376a9 typo 2024-06-13 22:05:34 +02:00
d26044ee3f added config file 2024-06-13 22:04:32 +02:00
47e8f0fc11 added config file 2024-06-13 22:04:03 +02:00
f02083d44c added custom entrypoint 2024-06-13 21:55:08 +02:00
66eaf91521 changed to prod 2024-06-13 16:28:03 +02:00
0704ea59b1 moved to env variables 2024-06-13 15:28:13 +02:00
848ee1ccb4 reversed longitude and latitude in api url 2024-06-13 14:53:27 +02:00
103e3bace3 reversed longitude and latitude in api url 2024-06-13 14:51:36 +02:00
088252f5bc reversed longitude and latitude in api url 2024-06-13 14:47:24 +02:00
9b8dc8cde7 changed tab name 2024-06-13 10:55:51 +02:00
5cb6ef0798 added sort for posts by date 2024-06-13 09:54:10 +02:00
9804bd196b added refresh and edit button 2024-06-13 08:59:35 +02:00
88bdfb35d3 added new favicon 2024-06-13 08:29:47 +02:00
52c8ab9eea made text areas not resizable 2024-06-13 08:24:34 +02:00
475c490b16 fixed wrong recursion comment count if there are not displayed comments 2024-06-13 08:21:25 +02:00
caf853e8a9 removed comment from last recursion layer 2024-06-13 08:15:46 +02:00
12 changed files with 163 additions and 56 deletions

View File

@@ -13,9 +13,14 @@ RUN npm install
FROM npm-installed AS builder
ADD . .
RUN npm run build
RUN npm run build --production
FROM nginx:stable-alpine3.17-slim
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh
COPY --from=builder /source/build /usr/share/nginx/html
ENTRYPOINT ["./entrypoint.sh"]

28
entrypoint.sh Normal file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
# vim:sw=4:ts=4:et
set -e
# Set up endpoint for env retrieval
echo "window._env_ = {" > /usr/share/nginx/html/env-config.js
# Collect enviroment variables for react
eval enviroment_variables="$(env | grep REACT_APP.*=)"
# Loop over variables
env | grep REACT_APP.*= | while read -r line;
do
printf "%s',\n" $line | sed "s/=/:'/" >> /usr/share/nginx/html/env-config.js
# Notify the user
printf "Env variable %s' was injected into React App. \n" $line | sed "0,/=/{s//:'/}"
done
# End the object creation
echo "}" >> /usr/share/nginx/html/env-config.js
echo "Enviroment Variable Injection Complete."
# Start Nginx
nginx -g "daemon off;"

4
public/env-config.js Normal file
View File

@@ -0,0 +1,4 @@
window._env_ = {
REACT_APP_API: "https://api.jodel.anxietyprime.de",
REACT_APP_KC: "https://keycloak.anxietyprime.de",
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 163 KiB

View File

@@ -9,7 +9,8 @@
name="description"
content="Web site created using create-react-app"
/>
<title>Jodel Klon</title>
<title>SWA-Jodel</title>
<script src="%PUBLIC_URL%/env-config.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -2,7 +2,7 @@ import React, {useEffect, useState} from 'react';
import NewPostForm from './components/NewPostForm';
import './App.scss';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faTimes} from "@fortawesome/free-solid-svg-icons";
import {faPenToSquare, faRotate, faTimes} from "@fortawesome/free-solid-svg-icons";
import {postApi} from "./api/posts.api";
import {locationUtils} from "./utils/location";
import Post from "./components/Post";
@@ -27,14 +27,21 @@ const App = () => {
const reload = async () => {
await loadPosts().then(data => {
setPosts(data);
await loadPosts().then(posts => {
posts = recursiveSort(posts);
setPosts(posts);
});
}
const recursiveSort = (posts) => {
posts.forEach((post, index) => {posts[index].comments = recursiveSort(post.comments)});
posts.sort((a, b) => a.date < b.date ? 1 : -1);
return posts;
}
const loadPosts = async () => {
const location = await locationUtils.getCurrentLocation();
return await postApi.getPostsByCoords(location.lon, location.lat);
return await postApi.getPostsByCoords(location.lat, location.lon);
}
const addPost = async (title, text) => {
@@ -62,12 +69,19 @@ const App = () => {
return (
<div className="app">
<button className="logout" onClick={() => keycloak.logout()}>Logout</button>
<div className="nav-bar">
<button className="nav-button" onClick={() => reload()}>
<FontAwesomeIcon className={"close-button"} icon={faRotate}/>
</button>
<button className="nav-button nav-space" onClick={() => keycloak.accountManagement()}>
<FontAwesomeIcon className={"close-button"} icon={faPenToSquare}/>
</button>
<button className="logout" onClick={() => keycloak.logout()}>Logout</button>
</div>
<h1>SWA - Jodel</h1>
<div style={{height: selectedPost?0:"auto", overflowY: "hidden"}}>
<NewPostForm addPost={addPost}/>
{posts.map(post => <Post key={post.id} post={post} recursionDepth={5} selectPost={selectPost} reload={reload} />)}
<div style={{height: selectedPost ? 0 : "auto", overflowY: "hidden"}}>
<NewPostForm addPost={addPost}/>
{posts.map(post => <Post key={post.id} post={post} recursionDepth={5} selectPost={selectPost} reload={reload} />)}
</div>
{selectedPost && (
<div className="single-post-view">

View File

@@ -63,12 +63,18 @@ h3 {
.post-content {
flex: 1;
margin-right: 20px;
white-space: pre-wrap;
}
.post-container {
margin-right: -5em;
}
.post-right {
display: flex;
flex-direction: column;
justify-content: space-evenly;
width: 5em;
.post-date {
position: relative;
@@ -85,6 +91,7 @@ h3 {
display: flex;
flex-direction: column;
align-items: center;
margin-top: -1em;
button {
padding: 10px;
@@ -251,6 +258,8 @@ button {
}
.nrOfComments {
position: relative;
right: -3em;
height: 100%;
display: flex;
align-self: flex-end;
@@ -259,7 +268,15 @@ button {
font-size: 1.5em;
padding: 0.35em;
* {
padding-right: 0.25em;
padding-right: 0.3em;
}
span {
padding-right: 0.6em;
}
button {
padding: 0.3em 0.8em;
line-height: 2em;
font-size: 0.8em;
}
}
@@ -267,7 +284,30 @@ button {
color: #ff9908;
}
textarea {
resize: none;
}
.nav-bar {
display: flex;
width: 100%;
flex-direction: row;
}
.nav-button {
height: 3em;
width: 3em;
}
.nav-space {
margin-left: auto;
}
.logout {
margin-left: 75%;
margin-left: 0.5em;
width: 25%
}
.spacer {
height: 4em;
}

View File

@@ -1,6 +1,10 @@
import Keycloak from "keycloak-js";
import getConfig from "../config/config";
const config = getConfig();
const keycloak = new Keycloak({
url: "https://keycloak.anxietyprime.de/",
url: config.REACT_APP_KC || process.env.REACT_APP_KC,
realm: "Jodel",
clientId: "jodel-frontend",
});

View File

@@ -1,13 +1,15 @@
import axios from "axios"
import {locationUtils} from "../utils/location";
import keycloak from "../Authentification/Keycloak";
import getConfig from "../config/config";
const path = "https://api.jodel.anxietyprime.de";
const config = getConfig();
const path = config.REACT_APP_API || process.env.REACT_APP_API;
export const postApi = {
async getPostsByCoords (lon, lat) {
async getPostsByCoords (lat, lon) {
if (!keycloak.authenticated) return [];
return (await axios.get(`${path}/posts/${lon}/${lat}`)).data;
return (await axios.get(`${path}/posts/${lat}/${lon}`)).data;
},
async getPostByID (id) {
if (!keycloak.authenticated) return [];

View File

@@ -7,8 +7,8 @@ import {
faAngleUp,
faAngleDown
} from '@fortawesome/free-solid-svg-icons';
import {faMessage} from "@fortawesome/free-solid-svg-icons/faMessage";
import {postApi} from "../api/posts.api";
import keycloak from "../Authentification/Keycloak";
const Post = ({ post, recursionDepth, selectPost, reload }) => {
@@ -32,62 +32,66 @@ const Post = ({ post, recursionDepth, selectPost, reload }) => {
postApi.reactToPost(post.id, null).then(reload);
}
const getComments = (post) => {
const getComments = (post, recursionDepth) => {
let nrOfComments = post.comments.length;
post.comments.forEach(comment => {
nrOfComments += getComments(comment);
if (recursionDepth !== 0) post.comments.forEach(comment => {
nrOfComments += getComments(comment, recursionDepth - 1);
})
return nrOfComments;
}
const getDate = (dateStr) => {
let date = new Date(dateStr);
return date.toLocaleDateString('de-DE') + "\n" + ("00" + date.getHours()).slice(-2) + ":" + ("00" + date.getMinutes()).slice(-2);
}
return (
<div>
<div className="post" onClick={click => {if (!(click.target instanceof HTMLButtonElement)) setShowComments(!showComments)}}>
<div className="post-container">
{post.id === post.parent && <div className="post-title">
<h3>{post.title}</h3>
</div>}
<div className="post-title">
<h3>{post.id === post.parent ? post.title : "Kommentar"}<span style={{color: 'gray'}}> von {post.anonymousID === 0 ? keycloak.idTokenParsed.preferred_username : "Anonymous " + post.anonymousID}</span></h3>
</div>
<div className="post-content">
<p>{post.content}</p>
</div>
</div>
<div className="nrOfComments">
{recursionDepth !== 0 && <div className="nrOfComments">
<FontAwesomeIcon className="icon" icon={faComments}/>
<span className="">{getComments(post)}</span>
</div>
<span className="">{getComments(post, recursionDepth - 1)}</span>
<button onClick={() => selectPost(post)}>Antworten</button>
</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>}
{getDate(post.date)}
</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 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 && <div className="spacer"></div>}
</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;

5
src/config/config.js Normal file
View File

@@ -0,0 +1,5 @@
const getConfig = () => {
return window._env_ || {};
}
export default getConfig;

View File

@@ -21,6 +21,6 @@ axios.interceptors.request.use(
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
<App/>
</React.StrictMode>
);