source: reactapp/src/Pages/Topic.js

main
Last change on this file was 8dffe02, checked in by unknown <mlviktor23@…>, 18 months ago

prefinal reproducible

  • Property mode set to 100644
File size: 21.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";
30import LoadingSpinner from "../Components/Styled/LoadingSpinner.style";
31
32const Topic = () => {
33 let params = useParams();
34 let navigate = useNavigate();
35 const { auth, setAuth } = useContext(AuthApi);
36
37 const [thread, setThread] = useState(null);
38 const [loadedThread, setLoadedThread] = useState(false);
39 const [user, setUser] = useState(null);
40 const [loadedUser, setLoadedUser] = useState(false);
41 const [fetchError, setFetchError] = useState(false);
42
43 const [postModalDisplay, setPostModalDisplay] = useState("none");
44 const [postContent, setPostContent] = useState("");
45
46 const [replyModalDisplay, setReplyModalDisplay] = useState("none");
47 const [replyContent, setReplyContent] = useState("");
48 const [postForReplyModal, setPostForReplyModal] = useState(null);
49
50 const [reportModalDisplay, setReportModalDisplay] = useState("none");
51 const [reportContent, setReportContent] = useState("");
52 const [postForReportModal, setPostForReportModal] = useState(null);
53
54 const [errorMessage, setErrorMessage] = useState("");
55
56 useEffect(() => {
57 const url1 = `http://192.168.1.108:8080/public/thread/${params.topicId}`;
58 const url2 = `http://192.168.1.108:8080/secure/currentUser`;
59
60 const fetchTopic = async () => {
61 try {
62 const response = await fetch(url1);
63 let cyclicGraph = await response.json();
64 let jsogStructure = JSOG.encode(cyclicGraph);
65 cyclicGraph = JSOG.decode(jsogStructure);
66 setThread(cyclicGraph);
67 setLoadedThread(true);
68 } catch (error) {
69 setFetchError(true);
70 }
71 };
72
73 const fetchUser = async () => {
74 try {
75 const response = await axios.get(url2, { withCredentials: true });
76 var cyclicGraph = await response.data;
77 var jsogStructure = JSOG.encode(cyclicGraph);
78 cyclicGraph = JSOG.decode(jsogStructure);
79 setUser(cyclicGraph);
80 setLoadedUser(true);
81 } catch (error) {
82 setFetchError(true);
83 }
84 };
85
86 fetchTopic().then(fetchUser);
87 }, [loadedThread]);
88
89 const handleReply = (post) => {
90 if (auth) {
91 setReplyModalDisplay("block");
92 setPostForReplyModal(post);
93 document.body.style.overflowY = "hidden";
94 } else {
95 navigate("/login");
96 }
97 };
98
99 const handleReport = (post) => {
100 if (auth) {
101 setReportModalDisplay("block");
102 setPostForReportModal(post);
103 document.body.style.overflowY = "hidden";
104 } else {
105 navigate("/login");
106 }
107 };
108
109 const handleReplyContentChange = (e) => {
110 setReplyContent(e.target.value);
111 };
112
113 const handleReportContentChange = (e) => {
114 e.preventDefault();
115 setReportContent(e.target.value);
116 };
117
118 const handleReplySubmit = async (e, postId) => {
119 e.preventDefault();
120
121 if (!replyContent.length < 1) {
122 const response = await axios(
123 `http://192.168.1.108:8080/secure/subject/${thread.targetSubject.subjectId}/replyToThread/${postId}`,
124 {
125 method: "post",
126 data: {
127 content: replyContent,
128 },
129 withCredentials: true,
130 }
131 );
132 setErrorMessage("");
133 window.location.reload();
134 } else {
135 setErrorMessage("Полето за содржина не смее да биде празно");
136 }
137 };
138
139 const handleReportSubmit = async (e, postId) => {
140 e.preventDefault();
141
142 if (!reportContent.length < 1) {
143 const response = await axios(
144 `http://192.168.1.108:8080/secure/reportThread/${postId}`,
145 {
146 method: "post",
147 data: {
148 description: reportContent,
149 },
150 withCredentials: true,
151 }
152 );
153 setErrorMessage("");
154 window.location.reload();
155 } else {
156 setErrorMessage("Полето за содржина не смее да биде празно");
157 }
158 };
159
160 const handleAddOpinionButtonClick = () => {
161 if (auth) {
162 setPostModalDisplay("block");
163 document.body.style.overflowY = "hidden";
164 } else {
165 navigate("/login");
166 }
167 };
168
169 const handlePostSubmit = async (e) => {
170 e.preventDefault();
171 if (!postContent.length < 1) {
172 const response = await axios(
173 `http://192.168.1.108:8080/secure/subject/${thread.targetSubject.subjectId}/replyToThread/${params.topicId}`,
174 {
175 method: "post",
176 data: {
177 content: postContent,
178 },
179 withCredentials: true,
180 }
181 );
182 setErrorMessage("");
183 window.location.reload();
184 } else {
185 setErrorMessage("Полето за содржина не смее да биде празно");
186 }
187 };
188 const handleModalCloseClick = () => {
189 setPostModalDisplay("none");
190 setReplyModalDisplay("none");
191 setReportModalDisplay("none");
192 document.body.style.overflowY = "auto";
193 };
194 const handleContentChange = (e) => {
195 setPostContent(e.target.value);
196 };
197
198 const handleLike = async (post) => {
199 if (auth) {
200 if (
201 loadedUser &&
202 user &&
203 !post.votes.some((e) => e.user.id === user.id)
204 ) {
205 const response = await axios(
206 `http://192.168.1.108:8080/secure/upvoteThread/${post.postId}`,
207 {
208 method: "get",
209 withCredentials: true,
210 }
211 );
212 window.location.reload();
213 } else {
214 return;
215 }
216 } else {
217 navigate("/login");
218 }
219 };
220
221 const handleDislike = async (post) => {
222 if (auth) {
223 if (
224 loadedUser &&
225 user &&
226 !post.votes.some((e) => e.user.id === user.id)
227 ) {
228 const response = await axios(
229 `http://192.168.1.108:8080/secure/downvoteThread/${post.postId}`,
230 {
231 method: "get",
232 withCredentials: true,
233 }
234 );
235
236 window.location.reload();
237 } else {
238 return;
239 }
240 } else {
241 navigate("/login");
242 }
243 };
244
245 function displayChildPosts(child, parentPostAuthorUsername, replyIndent) {
246 if (child == null) return;
247 //postCount = renderedOpinionIds.push(child.postId);
248 return (
249 <div key={child.postId}>
250 <OpinionReplyCard indent={replyIndent + "px"}>
251 <OpinionReplyCardContent>
252 <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
253 <a href={"/user/" + child.author.id}>{child.author.username}</a>{" "}
254 му реплицирал на {parentPostAuthorUsername}
255 </p>
256 <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
257 {child.content}
258 </p>
259 {new Date(child.timePosted).setMilliseconds(0) === new Date(child.timeLastEdited).setMilliseconds(0) ? (
260 <OpinionCardContentTime>
261 {dateConverter(
262 new Date(child.timePosted).toString().slice(4, -43)
263 )} <span style={{fontStyle:"normal",color:"blue"}}>#{child.postId}</span>
264 </OpinionCardContentTime>
265 ) : (
266 <OpinionCardContentTime>
267 {dateConverter(
268 new Date(child.timeLastEdited).toString().slice(4, -43)
269 )}{" "} <span style={{fontStyle:"normal",color:"blue"}}>#{child.postId}</span>{" "}
270 (едитирано од модератор)
271 </OpinionCardContentTime>
272 )}
273
274 <div
275 style={{
276 display:
277 !auth || (auth && loadedUser && user.id !== child.author.id)
278 ? "block"
279 : "none",
280 }}
281 >
282 <StyledFontAwesomeIcon
283 icon={solid("thumbs-up")}
284 right={50 + "px"}
285 color={
286 auth && loadedUser && user
287 ? child.votes.some(
288 (e) => e.vote === "UPVOTE" && e.user.id === user.id
289 )
290 ? "green"
291 : "darkgrey"
292 : "darkgrey"
293 }
294 onClick={() => handleLike(child)}
295 />
296
297 <VoteCount right={50 + "px"}>
298 {child.votes.filter((v) => v.vote === "UPVOTE").length}
299 </VoteCount>
300
301 <StyledFontAwesomeIcon
302 icon={solid("thumbs-down")}
303 right={10 + "px"}
304 color={
305 auth && loadedUser && user
306 ? child.votes.some(
307 (e) => e.vote === "DOWNVOTE" && e.user.id === user.id
308 )
309 ? "indianred"
310 : "darkgrey"
311 : "darkgrey"
312 }
313 onClick={() => handleDislike(child)}
314 />
315
316 <VoteCount right={10 + "px"}>
317 {child.votes.filter((v) => v.vote === "DOWNVOTE").length}
318 </VoteCount>
319
320 <StyledFontAwesomeIcon
321 icon={solid("reply")}
322 right={90 + "px"}
323 color="darkgrey"
324 onClick={() => handleReply(child)}
325 />
326
327 <StyledFontAwesomeIcon
328 icon={solid("flag")}
329 right={130 + "px"}
330 color="darkgrey"
331 onClick={() => handleReport(child)}
332 />
333 </div>
334 </OpinionReplyCardContent>
335
336 {child.children.map((childOfChild) =>
337 displayChildPosts(
338 childOfChild,
339 child.author.username,
340 replyIndent + 30
341 )
342 )}
343 </OpinionReplyCard>
344 </div>
345 );
346 }
347
348 return loadedThread && thread.length !== 0 ? (
349 <>
350 <CurrentPageNav>
351 &#187;{" "}
352 <a
353 href={
354 "/university/" +
355 thread.targetSubject.studyProgramme.faculty.university.universityId
356 }
357 >
358 {
359 thread.targetSubject.studyProgramme.faculty.university
360 .universityName
361 }
362 </a>{" "}
363 &#187;{" "}
364 <a
365 href={
366 "/faculty/" + thread.targetSubject.studyProgramme.faculty.facultyId
367 }
368 >
369 {thread.targetSubject.studyProgramme.faculty.facultyName}
370 </a>{" "}
371 &#187;{" "}
372 <a href={"/subject/" + thread.targetSubject.subjectId}>
373 {thread.targetSubject.subjectName}
374 </a>
375 </CurrentPageNav>
376 <div style={{ height: "20px", marginBottom: "50px", marginTop: "50px" }}>
377 <h3 style={{ float: "left" }}>{thread.title} <span style={{opacity:"50%", fontSize:"16px"}}>#{thread.postId}</span></h3>
378 {auth && (
379 <AddOpinionButton onClick={handleAddOpinionButtonClick}>
380 Реплицирај
381 </AddOpinionButton>
382 )}
383 </div>
384 <Modal display={postModalDisplay}>
385 <ModalContent>
386 <ModalHeader>
387 <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
388 <h3 style={{ marginTop: "5px" }}>
389 Реплика на темата {thread.title}
390 </h3>
391 </ModalHeader>
392 <form onSubmit={handlePostSubmit}>
393 <ModalBody>
394 <label htmlFor="content">
395 <b>Содржина</b>:
396 <ModalTextarea
397 id="content"
398 rows="8"
399 cols="100"
400 value={postContent}
401 onChange={handleContentChange}
402 spellCheck={false}
403 />
404 </label>
405 </ModalBody>
406 <p style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}>
407 {errorMessage}
408 </p>
409 <ModalFooter type="submit">ОБЈАВИ</ModalFooter>
410 </form>
411 </ModalContent>
412 </Modal>
413 <OpinionCard>
414 <OpinionCardContent>
415 <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
416 <a href={"/user/" + thread.author.id}>{thread.author.username}</a>{" "}
417 напишал
418 </p>
419 <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
420 {thread.content}
421 </p>
422 {new Date(thread.timePosted).setMilliseconds(0) === new Date(thread.timeLastEdited).setMilliseconds(0) ? (
423 <OpinionCardContentTime>
424 {dateConverter(
425 new Date(thread.timePosted).toString().slice(4, -43)
426 )} <span style={{fontStyle:"normal",color:"blue"}}>#{thread.postId}</span>
427 </OpinionCardContentTime>
428 ) : (
429 <OpinionCardContentTime>
430 {dateConverter(
431 new Date(thread.timeLastEdited).toString().slice(4, -43)
432 )}{" "} <span style={{fontStyle:"normal",color:"blue"}}>#{thread.postId}</span>{" "}
433 (едитирано од модератор)
434 </OpinionCardContentTime>
435 )}
436 <div
437 style={{
438 display:
439 !auth || (auth && loadedUser && user.id !== thread.author.id)
440 ? "block"
441 : "none",
442 }}
443 >
444 <StyledFontAwesomeIcon
445 icon={solid("flag")}
446 right={90 + "px"}
447 color="darkgrey"
448 onClick={() => handleReport(thread)}
449 />
450 <StyledFontAwesomeIcon
451 icon={solid("thumbs-up")}
452 right={50 + "px"}
453 color={
454 auth && loadedUser && user
455 ? thread.votes.some(
456 (e) => e.vote === "UPVOTE" && e.user.id === user.id
457 )
458 ? "green"
459 : "darkgrey"
460 : "darkgrey"
461 }
462 onClick={() => handleLike(thread)}
463 />
464
465 <VoteCount right={50 + "px"}>
466 {thread.votes.filter((v) => v.vote === "UPVOTE").length}
467 </VoteCount>
468
469 <StyledFontAwesomeIcon
470 icon={solid("thumbs-down")}
471 right={10 + "px"}
472 color={
473 auth && loadedUser && user
474 ? thread.votes.some(
475 (e) => e.vote === "DOWNVOTE" && e.user.id === user.id
476 )
477 ? "indianred"
478 : "darkgrey"
479 : "darkgrey"
480 }
481 onClick={() => handleDislike(thread)}
482 />
483
484 <VoteCount right={10 + "px"}>
485 {thread.votes.filter((v) => v.vote === "DOWNVOTE").length}
486 </VoteCount>
487 </div>
488 </OpinionCardContent>
489 </OpinionCard>
490 {thread.children.map((directChild) => {
491 return (
492 <OpinionCard key={directChild.postId}>
493 <OpinionCardContent>
494 <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
495 <a href={"/user/" + directChild.author.id}>
496 {directChild.author.username}
497 </a>{" "}
498 напишал
499 </p>
500 <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
501 {directChild.content}
502 </p>
503 {new Date(directChild.timePosted).setMilliseconds(0) === new Date(directChild.timeLastEdited).setMilliseconds(0) ? (
504 <OpinionCardContentTime>
505 {dateConverter(
506 new Date(directChild.timePosted).toString().slice(4, -43)
507 )} <span style={{fontStyle:"normal",color:"blue"}}>#{directChild.postId}</span>
508 </OpinionCardContentTime>
509 ) : (
510 <OpinionCardContentTime>
511 {dateConverter(
512 new Date(directChild.timeLastEdited)
513 .toString()
514 .slice(4, -43)
515 )}{" "} <span style={{fontStyle:"normal",color:"blue"}}>#{directChild.postId}</span>{" "}
516 (едитирано од модератор)
517 </OpinionCardContentTime>
518 )}
519 <div
520 style={{
521 display:
522 !auth ||
523 (auth && loadedUser && user.id !== directChild.author.id)
524 ? "block"
525 : "none",
526 }}
527 >
528 <StyledFontAwesomeIcon
529 icon={solid("thumbs-up")}
530 right={50 + "px"}
531 color={
532 auth && loadedUser && user
533 ? directChild.votes.some(
534 (e) => e.vote === "UPVOTE" && e.user.id === user.id
535 )
536 ? "green"
537 : "darkgrey"
538 : "darkgrey"
539 }
540 onClick={() => handleLike(directChild)}
541 />
542
543 <VoteCount right={50 + "px"}>
544 {directChild.votes.filter((v) => v.vote === "UPVOTE").length}
545 </VoteCount>
546
547 <StyledFontAwesomeIcon
548 icon={solid("thumbs-down")}
549 right={10 + "px"}
550 color={
551 auth && loadedUser && user
552 ? directChild.votes.some(
553 (e) => e.vote === "DOWNVOTE" && e.user.id === user.id
554 )
555 ? "indianred"
556 : "darkgrey"
557 : "darkgrey"
558 }
559 onClick={() => handleDislike(directChild)}
560 />
561
562 <VoteCount right={10 + "px"}>
563 {
564 directChild.votes.filter((v) => v.vote === "DOWNVOTE")
565 .length
566 }
567 </VoteCount>
568
569 <StyledFontAwesomeIcon
570 icon={solid("reply")}
571 right={90 + "px"}
572 color="darkgrey"
573 onClick={() => handleReply(directChild)}
574 />
575
576 <StyledFontAwesomeIcon
577 icon={solid("flag")}
578 right={130 + "px"}
579 color="darkgrey"
580 onClick={() => handleReport(directChild)}
581 />
582 </div>
583 </OpinionCardContent>
584 {directChild.children.map((child) =>
585 displayChildPosts(child, directChild.author.username, 30)
586 )}
587 </OpinionCard>
588 );
589 })}
590 {postForReplyModal && (
591 <Modal display={replyModalDisplay}>
592 <ModalContent>
593 <ModalHeader>
594 <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
595 <h3 style={{ marginTop: "5px" }}>
596 Реплика на {postForReplyModal.author.username}
597 </h3>
598 </ModalHeader>
599 <form onSubmit={(e) => handleReplySubmit(e, postForReplyModal.postId)}>
600 <ModalBody>
601 <label htmlFor="content">
602 <b>Содржина</b>:
603 <ModalTextarea
604 id="content"
605 rows="8"
606 cols="100"
607 value={replyContent}
608 onChange={handleReplyContentChange}
609 />
610 </label>
611 </ModalBody>
612 <p
613 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
614 >
615 {errorMessage}
616 </p>
617 <ModalFooter type="submit">РЕПЛИЦИРАЈ</ModalFooter>
618 </form>
619 </ModalContent>
620 </Modal>
621 )}
622 {postForReportModal && (
623 <Modal display={reportModalDisplay}>
624 <ModalContent>
625 <ModalHeader>
626 <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
627 <h3 style={{ marginTop: "5px" }}>
628 Пријава за мислење #{postForReportModal.postId}
629 </h3>
630 </ModalHeader>
631 <form onSubmit={(e) => handleReportSubmit(e, postForReportModal.postId)}>
632 <ModalBody>
633 <label htmlFor="content">
634 <b>Наведете причина</b>:
635 <ModalTextarea
636 id="content"
637 rows="8"
638 cols="100"
639 value={reportContent}
640 onChange={handleReportContentChange}
641 />
642 </label>
643 </ModalBody>
644 <p
645 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
646 >
647 {errorMessage}
648 </p>
649 <ModalFooter type="submit">ПРИЈАВИ</ModalFooter>
650 </form>
651 </ModalContent>
652 </Modal>
653 )}
654 </>
655 ) : !fetchError && !loadedThread ? (
656 <div>
657 <LoadingSpinner style={{ marginTop: "140px" }}/>
658 <Outlet />
659 </div>
660 ) : (
661 <div style={{ marginTop: "140px" }}>
662 <h1 style={{ textAlign: "center" }}>Страницата не е пронајдена.</h1>
663 </div>
664 );
665};
666
667export default Topic;
Note: See TracBrowser for help on using the repository browser.