source: reactapp/src/Pages/Topic.js@ c68150f

main
Last change on this file since c68150f was c68150f, checked in by unknown <mlviktor23@…>, 21 months ago

left: moderation, oAuth, messaging

  • Property mode set to 100644
File size: 16.7 KB
Line 
1import React, { useState, useEffect, useContext } from "react";
2import { useParams, Outlet } from "react-router-dom";
3import JSOG from "jsog";
4import { CurrentPageNav } from "../Components/Styled/Main.style";
5import AuthApi from "../api/AuthApi";
6import { useNavigate } from "react-router-dom";
7import {
8 OpinionCard,
9 OpinionCardContent,
10 OpinionCardContentTime,
11 OpinionReplyCard,
12 OpinionReplyCardContent,
13 OpinionReplyCardContentTime,
14 VoteCount,
15} from "../Components/Styled/OpinionCard.style";
16import { dateConverter } from "../Util/dateConverter";
17import { StyledFontAwesomeIcon } from "../Components/Styled/OpinionCard.style";
18import { solid } from "@fortawesome/fontawesome-svg-core/import.macro";
19import {
20 AddOpinionButton,
21 Modal,
22 ModalContent,
23 ModalHeader,
24 ModalClose,
25 ModalBody,
26 ModalTextarea,
27 ModalFooter,
28} from "../Components/Styled/Modal.style";
29import axios from "../api/axios";
30
31const Topic = () => {
32 let params = useParams();
33 let navigate = useNavigate();
34 const { auth, setAuth } = useContext(AuthApi);
35
36 const [thread, setThread] = useState(null);
37 const [loadedThread, setLoadedThread] = useState(false);
38 const [user, setUser] = useState(null);
39 const [loadedUser, setLoadedUser] = useState(false);
40 const [fetchError, setFetchError] = useState(false);
41
42 const [postModalDisplay, setPostModalDisplay] = useState("none");
43 const [postContent, setPostContent] = useState("");
44 const [replyModalDisplay, setReplyModalDisplay] = useState("none");
45 const [replyContent, setReplyContent] = useState("");
46 const [postForModal, setPostForModal] = useState(null);
47 const [errorMessage, setErrorMessage] = useState("");
48
49 useEffect(() => {
50 const url1 = `http://192.168.0.17:8080/public/thread/${params.topicId}`;
51 const url2 = `http://192.168.0.17:8080/secure/currentUser`;
52
53 const fetchTopic = async () => {
54 try {
55 const response = await fetch(url1);
56 let cyclicGraph = await response.json();
57 let jsogStructure = JSOG.encode(cyclicGraph);
58 cyclicGraph = JSOG.decode(jsogStructure);
59 setThread(cyclicGraph);
60 setLoadedThread(true);
61 } catch (error) {
62 setFetchError(true);
63 }
64 };
65
66 const fetchUser = async () => {
67 try {
68 const response = await axios.get(url2, { withCredentials: true });
69 var cyclicGraph = await response.data;
70 var jsogStructure = JSOG.encode(cyclicGraph);
71 cyclicGraph = JSOG.decode(jsogStructure);
72 setUser(cyclicGraph);
73 setLoadedUser(true);
74 } catch (error) {
75 setFetchError(true);
76 }
77 };
78
79 fetchTopic().then(fetchUser);
80 }, []);
81
82 const handleReply = (post) => {
83 if (auth) {
84 setReplyModalDisplay("block");
85 setPostForModal(post);
86 } else {
87 navigate("/login");
88 }
89 };
90
91 const handleReplyContentChange = (e) => {
92 setReplyContent(e.target.value);
93 };
94
95 const handleReplySubmit = async (e, postId) => {
96 e.preventDefault();
97
98 if (!replyContent.length < 1) {
99 const response = await axios(
100 `http://192.168.0.17:8080/secure/subject/${thread.targetSubject.subjectId}/replyToThread/${postId}`,
101 {
102 method: "post",
103 data: {
104 content: replyContent,
105 },
106 withCredentials: true,
107 }
108 );
109 setErrorMessage("");
110 window.location.reload(false);
111 } else {
112 setErrorMessage("Полето за содржина не смее да биде празно");
113 }
114 };
115
116 const handleAddOpinionButtonClick = () => {
117 if (auth) {
118 setPostModalDisplay("block");
119 } else {
120 navigate("/login");
121 }
122 };
123
124 const handlePostSubmit = async (e) => {
125 e.preventDefault();
126 if (!postContent.length < 1) {
127 const response = await axios(
128 `http://192.168.0.17:8080/secure/subject/${thread.targetSubject.subjectId}/replyToThread/${params.topicId}`,
129 {
130 method: "post",
131 data: {
132 content: postContent,
133 },
134 withCredentials: true,
135 }
136 );
137 setErrorMessage("");
138 window.location.reload(false);
139 } else {
140 setErrorMessage("Полето за содржина не смее да биде празно");
141 }
142 };
143 const handleModalCloseClick = () => {
144 setPostModalDisplay("none");
145 setReplyModalDisplay("none");
146 };
147 const handleContentChange = (e) => {
148 setPostContent(e.target.value);
149 };
150
151 const handleLike = async (post) => {
152 if (auth) {
153 if (
154 loadedUser &&
155 user &&
156 !post.votes.some((e) => e.user.id === user.id)
157 ) {
158 const response = await axios(
159 `http://192.168.0.17:8080/secure/upvoteThread/${post.postId}`,
160 {
161 method: "get",
162 withCredentials: true,
163 }
164 );
165 window.location.reload(false);
166 } else {
167 return;
168 }
169 } else {
170 navigate("/login");
171 }
172 };
173
174 const handleDislike = async (post) => {
175 if (auth) {
176 if (
177 loadedUser &&
178 user &&
179 !post.votes.some((e) => e.user.id === user.id)
180 ) {
181 const response = await axios(
182 `http://192.168.0.17:8080/secure/downvoteThread/${post.postId}`,
183 {
184 method: "get",
185 withCredentials: true,
186 }
187 );
188
189 window.location.reload(false);
190 } else {
191 return;
192 }
193 } else {
194 navigate("/login");
195 }
196 };
197
198 function displayChildPosts(child, parentPostAuthorUsername, replyIndent) {
199 if (child == null) return;
200 //postCount = renderedOpinionIds.push(child.postId);
201 return (
202 <div key={child.postId}>
203 <OpinionReplyCard indent={replyIndent + "px"}>
204 <OpinionReplyCardContent>
205 <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
206 <a href={"/user/" + child.author.id}>{child.author.username}</a>{" "}
207 му реплицирал на {parentPostAuthorUsername}
208 </p>
209 <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
210 {child.content}
211 </p>
212 <OpinionReplyCardContentTime>
213 {dateConverter(
214 new Date(child.timePosted).toString().slice(4, -43)
215 )}
216 </OpinionReplyCardContentTime>
217
218 <div
219 style={{
220 display:
221 !auth || (auth && loadedUser && user.id !== child.author.id)
222 ? "block"
223 : "none",
224 }}
225 >
226 <StyledFontAwesomeIcon
227 icon={solid("thumbs-up")}
228 right={50 + "px"}
229 color={
230 auth && loadedUser && user
231 ? child.votes.some(
232 (e) => e.vote === "UPVOTE" && e.user.id === user.id
233 )
234 ? "greenyellow"
235 : "darkgrey"
236 : "darkgrey"
237 }
238 onClick={() => handleLike(child)}
239 />
240
241 <VoteCount right={50 + "px"}>
242 {child.votes.filter((v) => v.vote === "UPVOTE").length}
243 </VoteCount>
244
245 <StyledFontAwesomeIcon
246 icon={solid("thumbs-down")}
247 right={10 + "px"}
248 color={
249 auth && loadedUser && user
250 ? child.votes.some(
251 (e) => e.vote === "DOWNVOTE" && e.user.id === user.id
252 )
253 ? "indianred"
254 : "darkgrey"
255 : "darkgrey"
256 }
257 onClick={() => handleDislike(child)}
258 />
259
260 <VoteCount right={50 + "px"}>
261 {child.votes.filter((v) => v.vote === "DOWNVOTE").length}
262 </VoteCount>
263
264 <StyledFontAwesomeIcon
265 icon={solid("reply")}
266 right={90 + "px"}
267 color="darkgrey"
268 onClick={() => handleReply(child)}
269 />
270 </div>
271 </OpinionReplyCardContent>
272
273 {child.children.map((childOfChild) =>
274 displayChildPosts(
275 childOfChild,
276 child.author.username,
277 replyIndent + 30
278 )
279 )}
280 </OpinionReplyCard>
281 </div>
282 );
283 }
284
285 return loadedThread && thread.length !== 0 ? (
286 <>
287 <CurrentPageNav>
288 &#187;{" "}
289 <a
290 href={
291 "/university/" +
292 thread.targetSubject.studyProgramme.faculty.university.universityId
293 }
294 >
295 {
296 thread.targetSubject.studyProgramme.faculty.university
297 .universityName
298 }
299 </a>{" "}
300 &#187;{" "}
301 <a
302 href={
303 "/faculty/" + thread.targetSubject.studyProgramme.faculty.facultyId
304 }
305 >
306 {thread.targetSubject.studyProgramme.faculty.facultyName}
307 </a>{" "}
308 &#187;{" "}
309 <a href={"/subject/" + thread.targetSubject.subjectId}>
310 {thread.targetSubject.subjectName}
311 </a>
312 </CurrentPageNav>
313 <div style={{ height: "20px", marginBottom: "50px", marginTop: "50px" }}>
314 <h3 style={{ float: "left" }}>{thread.title}</h3>
315 {auth && (
316 <AddOpinionButton onClick={handleAddOpinionButtonClick}>
317 Реплицирај
318 </AddOpinionButton>
319 )}
320 </div>
321 <Modal display={postModalDisplay}>
322 <ModalContent>
323 <ModalHeader>
324 <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
325 <h3 style={{ marginTop: "5px" }}>
326 Реплика на темата {thread.title}
327 </h3>
328 </ModalHeader>
329 <form onSubmit={handlePostSubmit}>
330 <ModalBody>
331 <label htmlFor="content">
332 <b>Содржина</b>:
333 <ModalTextarea
334 id="content"
335 rows="8"
336 cols="100"
337 value={postContent}
338 onChange={handleContentChange}
339 />
340 </label>
341 </ModalBody>
342 <p style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}>
343 {errorMessage}
344 </p>
345 <ModalFooter type="submit">ОБЈАВИ</ModalFooter>
346 </form>
347 </ModalContent>
348 </Modal>
349 <OpinionCard>
350 <OpinionCardContent>
351 <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
352 <a href={"/user/" + thread.author.id}>{thread.author.username}</a>{" "}
353 напишал
354 </p>
355 <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
356 {thread.content}
357 </p>
358 <OpinionCardContentTime>
359 {dateConverter(
360 new Date(thread.timePosted).toString().slice(4, -43)
361 )}
362 </OpinionCardContentTime>
363 <div
364 style={{
365 display:
366 !auth || (auth && loadedUser && user.id !== thread.author.id)
367 ? "block"
368 : "none",
369 }}
370 >
371 <StyledFontAwesomeIcon
372 icon={solid("thumbs-up")}
373 right={50 + "px"}
374 color={
375 auth && loadedUser && user
376 ? thread.votes.some(
377 (e) => e.vote === "UPVOTE" && e.user.id === user.id
378 )
379 ? "greenyellow"
380 : "darkgrey"
381 : "darkgrey"
382 }
383 onClick={() => handleLike(thread)}
384 />
385
386 <VoteCount right={50 + "px"}>
387 {thread.votes.filter((v) => v.vote === "UPVOTE").length}
388 </VoteCount>
389
390 <StyledFontAwesomeIcon
391 icon={solid("thumbs-down")}
392 right={10 + "px"}
393 color={
394 auth && loadedUser && user
395 ? thread.votes.some(
396 (e) => e.vote === "DOWNVOTE" && e.user.id === user.id
397 )
398 ? "indianred"
399 : "darkgrey"
400 : "darkgrey"
401 }
402 onClick={() => handleDislike(thread)}
403 />
404
405 <VoteCount right={10 + "px"}>
406 {thread.votes.filter((v) => v.vote === "DOWNVOTE").length}
407 </VoteCount>
408 </div>
409 </OpinionCardContent>
410 </OpinionCard>
411 {thread.children.map((directChild) => {
412 return (
413 <OpinionCard key={directChild.postId}>
414 <OpinionCardContent>
415 <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
416 <a href={"/user/" + directChild.author.id}>
417 {directChild.author.username}
418 </a>{" "}
419 напишал
420 </p>
421 <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
422 {directChild.content}
423 </p>
424 <OpinionCardContentTime>
425 {dateConverter(
426 new Date(directChild.timePosted).toString().slice(4, -43)
427 )}
428 </OpinionCardContentTime>
429 <div
430 style={{
431 display:
432 !auth ||
433 (auth && loadedUser && user.id !== directChild.author.id)
434 ? "block"
435 : "none",
436 }}
437 >
438 <StyledFontAwesomeIcon
439 icon={solid("thumbs-up")}
440 right={50 + "px"}
441 color={
442 auth && loadedUser && user
443 ? directChild.votes.some(
444 (e) => e.vote === "UPVOTE" && e.user.id === user.id
445 )
446 ? "greenyellow"
447 : "darkgrey"
448 : "darkgrey"
449 }
450 onClick={() => handleLike(directChild)}
451 />
452
453 <VoteCount right={50 + "px"}>
454 {directChild.votes.filter((v) => v.vote === "UPVOTE").length}
455 </VoteCount>
456
457 <StyledFontAwesomeIcon
458 icon={solid("thumbs-down")}
459 right={10 + "px"}
460 color={
461 auth && loadedUser && user
462 ? directChild.votes.some(
463 (e) => e.vote === "DOWNVOTE" && e.user.id === user.id
464 )
465 ? "indianred"
466 : "darkgrey"
467 : "darkgrey"
468 }
469 onClick={() => handleDislike(directChild)}
470 />
471
472 <VoteCount right={10 + "px"}>
473 {
474 directChild.votes.filter((v) => v.vote === "DOWNVOTE")
475 .length
476 }
477 </VoteCount>
478
479 <StyledFontAwesomeIcon
480 icon={solid("reply")}
481 right={90 + "px"}
482 color="darkgrey"
483 onClick={() => handleReply(directChild)}
484 />
485 </div>
486 </OpinionCardContent>
487 {directChild.children.map((child) =>
488 displayChildPosts(child, directChild.author.username, 30)
489 )}
490 </OpinionCard>
491 );
492 })}
493 {postForModal && (
494 <Modal display={replyModalDisplay}>
495 <ModalContent>
496 <ModalHeader>
497 <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
498 <h3 style={{ marginTop: "5px" }}>
499 Реплика на {postForModal.author.username}
500 </h3>
501 </ModalHeader>
502 <form onSubmit={(e) => handleReplySubmit(e, postForModal.postId)}>
503 <ModalBody>
504 <label htmlFor="content">
505 <b>Содржина</b>:
506 <ModalTextarea
507 id="content"
508 rows="8"
509 cols="100"
510 value={replyContent}
511 onChange={handleReplyContentChange}
512 />
513 </label>
514 </ModalBody>
515 <p
516 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
517 >
518 {errorMessage}
519 </p>
520 <ModalFooter type="submit">РЕПЛИЦИРАЈ</ModalFooter>
521 </form>
522 </ModalContent>
523 </Modal>
524 )}
525 </>
526 ) : !fetchError && !loadedThread ? (
527 <div>
528 <p style={{ marginTop: "140px" }}>се вчитува...</p>
529 <Outlet />
530 </div>
531 ) : (
532 <div style={{ marginTop: "140px" }}>
533 <h1 style={{ textAlign: "center" }}>Страницата не е пронајдена.</h1>
534 </div>
535 );
536};
537
538export default Topic;
Note: See TracBrowser for help on using the repository browser.