Compare commits
33 Commits
52c8ab9eea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8968e990ec | |||
| 94996fb45c | |||
| 29af08f79d | |||
| a6ef1c71e2 | |||
| 2f4eb05233 | |||
| 34e44d2310 | |||
| f2f096ef67 | |||
|
|
3f13bbdd19 | ||
| 1ca4235283 | |||
| 96753a774c | |||
| 625485d318 | |||
| eeae861353 | |||
| d524a831cd | |||
| 1f947e3200 | |||
| f92b7345ae | |||
| 6f9933f332 | |||
| 3936d1c651 | |||
| e173888692 | |||
| 621e414f95 | |||
| 454973b017 | |||
| 17a29376a9 | |||
| d26044ee3f | |||
| 47e8f0fc11 | |||
| f02083d44c | |||
| 66eaf91521 | |||
| 0704ea59b1 | |||
| 848ee1ccb4 | |||
| 103e3bace3 | |||
| 088252f5bc | |||
| 9b8dc8cde7 | |||
| 5cb6ef0798 | |||
| 9804bd196b | |||
| 88bdfb35d3 |
@@ -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
28
entrypoint.sh
Normal 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
4
public/env-config.js
Normal 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 |
@@ -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>
|
||||
|
||||
32
src/App.js
32
src/App.js
@@ -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">
|
||||
|
||||
44
src/App.scss
44
src/App.scss
@@ -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,11 +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%
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
.spacer {
|
||||
height: 4em;
|
||||
}
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -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 [];
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -40,54 +40,58 @@ const Post = ({ post, recursionDepth, selectPost, reload }) => {
|
||||
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>
|
||||
{recursionDepth !== 0 && <div className="nrOfComments">
|
||||
<FontAwesomeIcon className="icon" icon={faComments}/>
|
||||
<span className="">{getComments(post, recursionDepth-1)}</span>
|
||||
<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
5
src/config/config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const getConfig = () => {
|
||||
return window._env_ || {};
|
||||
}
|
||||
|
||||
export default getConfig;
|
||||
@@ -21,6 +21,6 @@ axios.interceptors.request.use(
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<App/>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user