source: reactapp/src/Pages/Topic.js@ 3b6962d

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

moderation/reporting api in spring boot

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