source: reactapp/src/Components/OpinionTree.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: 15.5 KB
Line 
1import {
2 OpinionCard,
3 OpinionCardContent,
4 OpinionCardContentTime,
5 OpinionReplyCard,
6 OpinionReplyCardContent,
7 OpinionReplyCardContentTime,
8 StyledFontAwesomeIcon,
9 VoteCount,
10} from "./Styled/OpinionCard.style";
11import { solid } from "@fortawesome/fontawesome-svg-core/import.macro";
12import { dateConverter } from "../Util/dateConverter";
13import AuthApi from "../api/AuthApi";
14import { useNavigate } from "react-router-dom";
15import { useContext, useState, useEffect } from "react";
16import JSOG from "jsog";
17import {
18 Modal,
19 ModalContent,
20 ModalClose,
21 ModalHeader,
22 ModalBody,
23 ModalTextarea,
24 ModalFooter,
25} from "../Components/Styled/Modal.style";
26import axios from "../api/axios";
27
28const OpinionTree = ({professor, relatedOpinions}) => {
29 var renderedOpinionIds = [];
30 var postCount; // za da ne go pokazuva ispod postot
31
32 const { auth, setAuth } = useContext(AuthApi);
33 let navigate = useNavigate();
34
35 const [replyModalDisplay, setReplyModalDisplay] = useState("none");
36 const [replyContent, setReplyContent] = useState("");
37 const [postForReplyModal, setPostForReplyModal] = useState(null);
38 const [reportModalDisplay, setReportModalDisplay] = useState("none");
39 const [reportContent, setReportContent] = useState("")
40 const [postForReportModal, setPostForReportModal] = useState(null);
41
42 const [user, setUser] = useState(null);
43 const [loadedUser, setLoadedUser] = useState(false);
44 const [fetchError, setFetchError] = useState(false);
45 const [errorMessage, setErrorMessage] = useState("");
46
47 useEffect(() => {
48 const url = `http://192.168.1.108:8080/secure/currentUser`;
49
50 const fetchUser = async () => {
51 try {
52 const response = await axios.get(url, { withCredentials: true });
53 var cyclicGraph = await response.data;
54 var jsogStructure = JSOG.encode(cyclicGraph);
55 cyclicGraph = JSOG.decode(jsogStructure);
56 setUser(cyclicGraph);
57 setLoadedUser(true);
58 } catch (error) {
59 setFetchError(true);
60 }
61 };
62
63 if (auth) fetchUser();
64 }, []);
65
66 const handleLike = async (post) => {
67 if (auth) {
68 if (
69 loadedUser &&
70 user &&
71 !post.votes.some((e) => e.user.id === user.id)
72 ) {
73 const response = await axios(
74 `http://192.168.1.108:8080/secure/upvoteOpinion/${post.postId}`,
75 {
76 method: "get",
77 withCredentials: true,
78 }
79 );
80 window.location.reload();
81 } else {
82 return;
83 }
84 } else {
85 navigate("/login");
86 }
87 };
88
89 const handleDislike = async (post) => {
90 if (auth) {
91 if (
92 loadedUser &&
93 user &&
94 !post.votes.some((e) => e.user.id === user.id)
95 ) {
96 const response = await axios(
97 `http://192.168.1.108:8080/secure/downvoteOpinion/${post.postId}`,
98 {
99 method: "get",
100 withCredentials: true,
101 }
102 );
103
104 window.location.reload();
105 } else {
106 return;
107 }
108 } else {
109 navigate("/login");
110 }
111 };
112
113 const handleReply = (opinion) => {
114 if (auth) {
115 setReplyModalDisplay("block");
116 setPostForReplyModal(opinion);
117 document.body.style.overflowY = "hidden";
118 } else {
119 navigate("/login");
120 }
121 };
122
123 const handleReport = (opinion) => {
124 if (auth) {
125 setReportModalDisplay("block");
126 setPostForReportModal(opinion);
127 document.body.style.overflowY = "hidden";
128 } else {
129 navigate("/login");
130 }
131 }
132
133 const handleModalCloseClick = () => {
134 setReplyModalDisplay("none");
135 setReportModalDisplay("none");
136 document.body.style.overflowY = "auto";
137 };
138
139 const handleReplyContentChange = (e) => {
140 setReplyContent(e.target.value);
141 };
142
143 const handleReportContentChange = (e) => {
144 setReportContent(e.target.value);
145 };
146
147 const handleReplySubmit = async (e, postId) => {
148 e.preventDefault();
149
150 if (!replyContent.length < 1) {
151 const response = await axios(
152 `http://192.168.1.108:8080/secure/professor/${professor.professorId}/replyToOpinion/${postId}`,
153 {
154 method: "post",
155 data: {
156 content: replyContent,
157 },
158 withCredentials: true,
159 }
160 );
161 setErrorMessage("");
162 window.location.reload();
163 } else {
164 setErrorMessage("Полето за содржина не смее да биде празно");
165 }
166 };
167
168 const handleReportSubmit = async (e, postId) => {
169 e.preventDefault();
170
171 if (!reportContent.length < 1) {
172 const response = await axios(
173 `http://192.168.1.108:8080/secure/reportOpinion/${postId}`,
174 {
175 method: "post",
176 data: {
177 description: reportContent,
178 },
179 withCredentials: true,
180 }
181 );
182 setErrorMessage("");
183 window.location.reload();
184 } else {
185 setErrorMessage("Полето за содржина не смее да биде празно");
186 }
187 };
188
189 function displayChildPosts(child, parentPostAuthorUsername, replyIndent) {
190 if (child == null) return;
191 if (!renderedOpinionIds.includes(child.postId)) {postCount = renderedOpinionIds.push(child.postId);}
192 return (
193 <div key={child.postId}>
194 <OpinionReplyCard indent={replyIndent + "px"}>
195 <OpinionReplyCardContent>
196 <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
197 <a href={"/user/" + child.author.id}>{child.author.username}</a>{" "}
198 му реплицирал на {parentPostAuthorUsername}
199 </p>
200 <p style={{ marginBottom: "10px", maxWidth: "85%" }}>
201 {child.content}
202 </p>
203 {new Date(child.timePosted).setMilliseconds(0) === new Date(child.timeLastEdited).setMilliseconds(0) ? (
204 <OpinionCardContentTime>
205 {dateConverter(
206 new Date(child.timePosted).toString().slice(4, -43)
207 )} <span style={{fontStyle:"normal",color:"blue"}}>#{child.postId}</span>
208 </OpinionCardContentTime>
209 ) : (
210 <OpinionCardContentTime>
211 {dateConverter(
212 new Date(child.timeLastEdited).toString().slice(4, -43)
213 )}{" "} <span style={{fontStyle:"normal",color:"blue"}}>#{child.postId}</span>{" "}
214 (едитирано од модератор)
215 </OpinionCardContentTime>
216 )}
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 ? "green"
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={10 + "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
271 <StyledFontAwesomeIcon
272 icon={solid("flag")}
273 right={130 + "px"}
274 color="darkgrey"
275 onClick={() => handleReport(child)}
276 />
277 </div>
278 </OpinionReplyCardContent>
279 {child.children.map((childOfChild) =>
280 displayChildPosts(
281 childOfChild,
282 child.author.username,
283 replyIndent + 30
284 )
285 )}
286 </OpinionReplyCard>
287 </div>
288 );
289 }
290
291 return (
292 <div className="opinionTree">
293 {relatedOpinions.map((opinion) => {
294 if (!renderedOpinionIds.includes(opinion.postId) && opinion.parent === null) {
295 postCount = renderedOpinionIds.push(opinion.postId);
296 return (
297 <div key={opinion.postId}>
298 <OpinionCard>
299 <OpinionCardContent>
300 <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
301 <a href={"/user/" + opinion.author.id}>
302 {opinion.author.username}
303 </a>{" "}
304 напишал
305 </p>
306 <p style={{ marginBottom: "10px", maxWidth: "85%" }}>
307 {opinion.content}
308 </p>
309 {new Date(opinion.timePosted).setMilliseconds(0) === new Date(opinion.timeLastEdited).setMilliseconds(0) ? (
310 <OpinionCardContentTime>
311 {dateConverter(
312 new Date(opinion.timePosted).toString().slice(4, -43)
313 )} <span style={{fontStyle:"normal",color:"blue"}}>#{opinion.postId}</span>
314 </OpinionCardContentTime>
315 ) : (
316 <OpinionCardContentTime>
317 {dateConverter(
318 new Date(opinion.timeLastEdited)
319 .toString()
320 .slice(4, -43)
321 )}{" "} <span style={{fontStyle:"normal",color:"blue"}}>#{opinion.postId}</span>{" "}
322 (едитирано од модератор)
323 </OpinionCardContentTime>
324 )}
325
326 <div
327 style={{
328 display:
329 !auth ||
330 (auth && loadedUser && user.id !== opinion.author.id)
331 ? "block"
332 : "none",
333 }}
334 >
335 <StyledFontAwesomeIcon
336 icon={solid("thumbs-up")}
337 right={50 + "px"}
338 color={
339 auth && loadedUser && user
340 ? opinion.votes.some(
341 (e) =>
342 e.vote === "UPVOTE" && e.user.id === user.id
343 )
344 ? "green"
345 : "darkgrey"
346 : "darkgrey"
347 }
348 onClick={() => handleLike(opinion)}
349 />
350
351 <VoteCount right={50 + "px"}>
352 {opinion.votes.filter((v) => v.vote === "UPVOTE").length}
353 </VoteCount>
354
355 <StyledFontAwesomeIcon
356 icon={solid("thumbs-down")}
357 right={10 + "px"}
358 color={
359 auth && loadedUser && user
360 ? opinion.votes.some(
361 (e) =>
362 e.vote === "DOWNVOTE" && e.user.id === user.id
363 )
364 ? "indianred"
365 : "darkgrey"
366 : "darkgrey"
367 }
368 onClick={() => handleDislike(opinion)}
369 />
370
371 <VoteCount right={10 + "px"}>
372 {
373 opinion.votes.filter((v) => v.vote === "DOWNVOTE")
374 .length
375 }
376 </VoteCount>
377
378 <StyledFontAwesomeIcon
379 icon={solid("reply")}
380 right={90 + "px"}
381 color="darkgrey"
382 onClick={() => handleReply(opinion)}
383 />
384
385 <StyledFontAwesomeIcon
386 icon={solid("flag")}
387 right={130 + "px"}
388 color="darkgrey"
389 onClick={() => handleReport(opinion)}
390 />
391 </div>
392 </OpinionCardContent>
393 {opinion.children.map((child) =>
394 displayChildPosts(child, opinion.author.username, 30)
395 )}
396 </OpinionCard>
397 </div>
398 );
399 }
400 return null;
401 })}
402 {postForReplyModal && (
403 <Modal display={replyModalDisplay}>
404 <ModalContent>
405 <ModalHeader>
406 <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
407 <h3 style={{ marginTop: "5px" }}>
408 Реплика на {postForReplyModal.author.username}
409 </h3>
410 </ModalHeader>
411 <form onSubmit={(e) => handleReplySubmit(e, postForReplyModal.postId)}>
412 <ModalBody>
413 <label htmlFor="content">
414 <b>Содржина</b>:
415 <ModalTextarea
416 id="content"
417 rows="8"
418 cols="100"
419 value={replyContent}
420 onChange={handleReplyContentChange}
421 spellCheck={false}
422 />
423 </label>
424 </ModalBody>
425 <p
426 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
427 >
428 {errorMessage}
429 </p>
430 <ModalFooter type="submit">РЕПЛИЦИРАЈ</ModalFooter>
431 </form>
432 </ModalContent>
433 </Modal>
434 )}
435 {postForReportModal && (
436 <Modal display={reportModalDisplay}>
437 <ModalContent>
438 <ModalHeader>
439 <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
440 <h3 style={{ marginTop: "5px" }}>
441 Пријава за мислење #{postForReportModal.postId}
442 </h3>
443 </ModalHeader>
444 <form onSubmit={(e) => handleReportSubmit(e, postForReportModal.postId)}>
445 <ModalBody>
446 <label htmlFor="content">
447 <b>Наведете причина</b>:
448 <ModalTextarea
449 id="content"
450 rows="8"
451 cols="100"
452 value={reportContent}
453 onChange={handleReportContentChange}
454 spellCheck={false}
455 />
456 </label>
457 </ModalBody>
458 <p
459 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
460 >
461 {errorMessage}
462 </p>
463 <ModalFooter type="submit">ПРИЈАВИ</ModalFooter>
464 </form>
465 </ModalContent>
466 </Modal>
467 )}
468 </div>
469 );
470}
471
472export default OpinionTree;
Note: See TracBrowser for help on using the repository browser.