- Timestamp:
- 10/27/22 17:35:03 (2 years ago)
- Branches:
- main
- Children:
- 3b6962d
- Parents:
- 8d83180
- Location:
- reactapp/src
- Files:
-
- 1 added
- 15 edited
Legend:
- Unmodified
- Added
- Removed
-
reactapp/src/App.js
r8d83180 rc68150f 12 12 import AuthApi from "./api/AuthApi"; 13 13 import Cookies from "js-cookie"; 14 import axios from "./api/axios";15 import JSOG from "jsog";16 14 import NotFound from "./Pages/NotFound"; 15 import Topic from "./Pages/Topic"; 17 16 18 17 export default function App() { 19 18 const [auth, setAuth] = useState(false); 20 const [user, setUser] = useState(null);21 const [userLoaded, setUserLoaded] = useState(false);22 19 const variableAuth = useMemo(() => ({ auth, setAuth }), [auth]); 23 20 const [authLoaded, setAuthLoaded] = useState(false); 24 25 const fetchUser = async () => {26 try {27 const response = await axios.get(28 "http://192.168.0.17:8080/secure/currentUser",29 { withCredentials: true }30 );31 var cyclicGraph = await response.data;32 var jsogStructure = JSOG.encode(cyclicGraph);33 cyclicGraph = JSOG.decode(jsogStructure);34 setUser(cyclicGraph);35 setUserLoaded(true);36 } catch (error) {37 console.log("Fetching error", error);38 }39 };40 21 41 22 const readCookie = async () => { … … 43 24 if (session) { 44 25 setAuth(true); 45 fetchUser();46 26 } else { 47 27 setAuth(false); … … 51 31 52 32 useEffect(() => { 33 document.title = "profesori.mk"; 53 34 readCookie(); 54 35 }, []); … … 69 50 <BrowserRouter> 70 51 <Routes> 71 <Route 72 path="/" 73 element={<Home user={user} userLoaded={userLoaded} />} 74 > 52 <Route path="/" element={<Home />}> 75 53 <Route path="login" element={<Login />}></Route> 76 54 <Route path="registration" element={<Registration />}></Route> 77 55 <Route path="professor"> 78 <Route 79 path=":professorId" 80 element={<Professor user={user} userLoaded={userLoaded} />} 81 /> 56 <Route path=":professorId" element={<Professor />} /> 82 57 </Route> 83 58 <Route path="university/:universityId" element={<University />} /> 84 59 <Route path="faculty/:facultyId" element={<Faculty />} /> 85 60 <Route path="subject/:subjectId" element={<Subject />} /> 61 <Route path="topic/:topicId" element={<Topic />} /> 86 62 <Route path="search" element={<SearchResults />}></Route> 87 63 <Route 88 64 path="user_dashboard" 89 65 element={ 90 <ProtectedRoute auth={auth}> 91 {<UserDashboard user={user} userLoaded={userLoaded} />} 92 </ProtectedRoute> 66 <ProtectedRoute auth={auth}>{<UserDashboard />}</ProtectedRoute> 93 67 } 94 68 ></Route> -
reactapp/src/Components/OpinionTree.js
r8d83180 rc68150f 3 3 OpinionCardContent, 4 4 OpinionCardContentTime, 5 OpinionCardContentTitle,6 5 OpinionReplyCard, 7 6 OpinionReplyCardContent, … … 14 13 import AuthApi from "../api/AuthApi"; 15 14 import { useNavigate } from "react-router-dom"; 16 import { useContext, useState } from "react"; 15 import { useContext, useState, useEffect } from "react"; 16 import JSOG from "jsog"; 17 17 import { 18 18 Modal, … … 26 26 import axios from "../api/axios"; 27 27 28 function OpinionTree({ professor , user, userLoaded}) {28 function OpinionTree({ professor }) { 29 29 var renderedOpinionIds = []; 30 30 var postCount; // za da ne go pokazuva ispod postot 31 31 32 const { auth, setAuth } = useContext(AuthApi); 32 33 let navigate = useNavigate(); 33 const { auth, setAuth } = useContext(AuthApi); 34 35 let [replyModalDisplay, setReplyModalDisplay] = useState("none"); 34 35 const [replyModalDisplay, setReplyModalDisplay] = useState("none"); 36 36 const [replyContent, setReplyContent] = useState(""); 37 38 37 const [postForModal, setPostForModal] = useState(null); 38 const [user, setUser] = useState(null); 39 const [loadedUser, setLoadedUser] = useState(false); 40 const [fetchError, setFetchError] = useState(false); 41 const [errorMessage, setErrorMessage] = useState(""); 42 43 useEffect(() => { 44 const url = `http://192.168.0.17:8080/secure/currentUser`; 45 46 const fetchUser = async () => { 47 try { 48 const response = await axios.get(url, { withCredentials: true }); 49 var cyclicGraph = await response.data; 50 var jsogStructure = JSOG.encode(cyclicGraph); 51 cyclicGraph = JSOG.decode(jsogStructure); 52 setUser(cyclicGraph); 53 setLoadedUser(true); 54 } catch (error) { 55 setFetchError(true); 56 } 57 }; 58 59 if (auth) fetchUser(); 60 }, []); 39 61 40 62 const handleLike = async (post) => { 41 if ( 42 auth && 43 userLoaded && 44 !post.likes.some((e) => e.id === user.user.id) && 45 !post.dislikes.some((e) => e.id === user.user.id) 46 ) { 47 const response = await axios( 48 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/upvoteOpinion/${post.postId}`, 49 { 50 method: "get", 51 withCredentials: true, 52 } 53 ); 54 55 window.location.reload(false); 63 if (auth) { 64 if ( 65 loadedUser && 66 user && 67 !post.votes.some((e) => e.user.id === user.id) 68 ) { 69 const response = await axios( 70 `http://192.168.0.17:8080/secure/upvoteOpinion/${post.postId}`, 71 { 72 method: "get", 73 withCredentials: true, 74 } 75 ); 76 window.location.reload(false); 77 } else { 78 return; 79 } 56 80 } else { 57 return;81 navigate("/login"); 58 82 } 59 83 }; 60 84 61 85 const handleDislike = async (post) => { 62 if ( 63 auth && 64 auth && 65 userLoaded && 66 !post.likes.some((e) => e.id === user.user.id) && 67 !post.dislikes.some((e) => e.id === user.user.id) 68 ) { 69 const response = await axios( 70 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/downvoteOpinion/${post.postId}`, 71 { 72 method: "get", 73 withCredentials: true, 74 } 75 ); 76 77 window.location.reload(false); 86 if (auth) { 87 if ( 88 loadedUser && 89 user && 90 !post.votes.some((e) => e.user.id === user.id) 91 ) { 92 const response = await axios( 93 `http://192.168.0.17:8080/secure/downvoteOpinion/${post.postId}`, 94 { 95 method: "get", 96 withCredentials: true, 97 } 98 ); 99 100 window.location.reload(false); 101 } else { 102 return; 103 } 78 104 } else { 79 return;105 navigate("/login"); 80 106 } 81 107 }; … … 101 127 e.preventDefault(); 102 128 103 const response = await axios( 104 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/replyToOpinion/${postId}`, 105 { 106 method: "post", 107 body: { 108 content: replyContent, 109 }, 110 withCredentials: true, 111 } 112 ); 113 114 window.location.reload(false); 115 //console.log(response); 129 if (!replyContent.length < 1) { 130 const response = await axios( 131 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/replyToOpinion/${postId}`, 132 { 133 method: "post", 134 data: { 135 content: replyContent, 136 }, 137 withCredentials: true, 138 } 139 ); 140 setErrorMessage(""); 141 window.location.reload(false); 142 } else { 143 setErrorMessage("Полето за содржина не смее да биде празно"); 144 } 116 145 }; 117 146 … … 123 152 <OpinionReplyCard indent={replyIndent + "px"}> 124 153 <OpinionReplyCardContent> 125 <p >126 <a href= "#">{child.author.username}</a> му реплицирал на{" "}127 {parentPostAuthorUsername}154 <p style={{ fontStyle: "italic", marginBottom: "10px" }}> 155 <a href={"/user/" + child.author.id}>{child.author.username}</a>{" "} 156 му реплицирал на {parentPostAuthorUsername} 128 157 </p> 129 <p>{child.content}</p> 158 <p style={{ marginBottom: "10px", maxWidth: "90%" }}> 159 {child.content} 160 </p> 130 161 <OpinionReplyCardContentTime> 131 162 {dateConverter( … … 133 164 )} 134 165 </OpinionReplyCardContentTime> 135 {auth && userLoaded && user.user.id !== child.author.id && ( 136 <> 137 <StyledFontAwesomeIcon 138 icon={solid("thumbs-up")} 139 right={50 + "px"} 140 color={ 141 child.likes.some((e) => e.id === user.user.id) 166 167 <div 168 style={{ 169 display: 170 !auth || (auth && loadedUser && user.id !== child.author.id) 171 ? "block" 172 : "none", 173 }} 174 > 175 <StyledFontAwesomeIcon 176 icon={solid("thumbs-up")} 177 right={50 + "px"} 178 color={ 179 auth && loadedUser && user 180 ? child.votes.some( 181 (e) => e.vote === "UPVOTE" && e.user.id === user.id 182 ) 142 183 ? "greenyellow" 143 184 : "darkgrey" 144 } 145 onClick={() => handleLike(child)} 146 /> 147 <VoteCount right={50 + "px"}>{child.likes.length}</VoteCount> 148 <StyledFontAwesomeIcon 149 icon={solid("thumbs-down")} 150 right={10 + "px"} 151 color={ 152 child.dislikes.some((e) => e.id === user.user.id) 185 : "darkgrey" 186 } 187 onClick={() => handleLike(child)} 188 /> 189 190 <VoteCount right={50 + "px"}> 191 {child.votes.filter((v) => v.vote === "UPVOTE").length} 192 </VoteCount> 193 194 <StyledFontAwesomeIcon 195 icon={solid("thumbs-down")} 196 right={10 + "px"} 197 color={ 198 auth && loadedUser && user 199 ? child.votes.some( 200 (e) => e.vote === "DOWNVOTE" && e.user.id === user.id 201 ) 153 202 ? "indianred" 154 203 : "darkgrey" 155 } 156 onClick={() => handleDislike(child)} 157 /> 158 <VoteCount right={10 + "px"}>{child.dislikes.length}</VoteCount> 159 <StyledFontAwesomeIcon 160 icon={solid("reply")} 161 right={90 + "px"} 162 color="darkgrey" 163 onClick={() => handleReply(child)} 164 /> 165 </> 166 )} 204 : "darkgrey" 205 } 206 onClick={() => handleDislike(child)} 207 /> 208 209 <VoteCount right={10 + "px"}> 210 {child.votes.filter((v) => v.vote === "DOWNVOTE").length} 211 </VoteCount> 212 213 <StyledFontAwesomeIcon 214 icon={solid("reply")} 215 right={90 + "px"} 216 color="darkgrey" 217 onClick={() => handleReply(child)} 218 /> 219 </div> 167 220 </OpinionReplyCardContent> 168 221 {child.children.map((childOfChild) => … … 187 240 <OpinionCard> 188 241 <OpinionCardContent> 189 <p> 190 <a href="#">{opinion.author.username}</a> напишал 242 <p style={{ fontStyle: "italic", marginBottom: "10px" }}> 243 <a href={"/user/" + opinion.author.id}> 244 {opinion.author.username} 245 </a>{" "} 246 напишал 191 247 </p> 192 <OpinionCardContentTitle> 193 {opinion.title} 194 </OpinionCardContentTitle> 195 <p>{opinion.content}</p> 248 <p style={{ marginBottom: "10px", maxWidth: "90%" }}> 249 {opinion.content} 250 </p> 196 251 <OpinionCardContentTime> 197 252 {dateConverter( … … 199 254 )} 200 255 </OpinionCardContentTime> 201 {auth && userLoaded && user.user.id !== opinion.author.id && ( 202 <> 203 <StyledFontAwesomeIcon 204 icon={solid("thumbs-up")} 205 right={50 + "px"} 206 color={ 207 opinion.likes.some((e) => e.id === user.user.id) 256 257 <div 258 style={{ 259 display: 260 !auth || 261 (auth && loadedUser && user.id !== opinion.author.id) 262 ? "block" 263 : "none", 264 }} 265 > 266 <StyledFontAwesomeIcon 267 icon={solid("thumbs-up")} 268 right={50 + "px"} 269 color={ 270 auth && loadedUser && user 271 ? opinion.votes.some( 272 (e) => 273 e.vote === "UPVOTE" && e.user.id === user.id 274 ) 208 275 ? "greenyellow" 209 276 : "darkgrey" 210 } 211 onClick={() => handleLike(opinion)} 212 /> 213 <VoteCount right={50 + "px"}> 214 {opinion.likes.length} 215 </VoteCount> 216 <StyledFontAwesomeIcon 217 icon={solid("thumbs-down")} 218 right={10 + "px"} 219 color={ 220 opinion.dislikes.some((e) => e.id === user.user.id) 277 : "darkgrey" 278 } 279 onClick={() => handleLike(opinion)} 280 /> 281 282 <VoteCount right={50 + "px"}> 283 {opinion.votes.filter((v) => v.vote === "UPVOTE").length} 284 </VoteCount> 285 286 <StyledFontAwesomeIcon 287 icon={solid("thumbs-down")} 288 right={10 + "px"} 289 color={ 290 auth && loadedUser && user 291 ? opinion.votes.some( 292 (e) => 293 e.vote === "DOWNVOTE" && e.user.id === user.id 294 ) 221 295 ? "indianred" 222 296 : "darkgrey" 223 } 224 onClick={() => handleDislike(opinion)} 225 /> 226 <VoteCount right={10 + "px"}> 227 {opinion.dislikes.length} 228 </VoteCount> 229 <StyledFontAwesomeIcon 230 icon={solid("reply")} 231 right={90 + "px"} 232 color="darkgrey" 233 onClick={() => handleReply(opinion)} 234 /> 235 </> 236 )} 297 : "darkgrey" 298 } 299 onClick={() => handleDislike(opinion)} 300 /> 301 302 <VoteCount right={10 + "px"}> 303 { 304 opinion.votes.filter((v) => v.vote === "DOWNVOTE") 305 .length 306 } 307 </VoteCount> 308 309 <StyledFontAwesomeIcon 310 icon={solid("reply")} 311 right={90 + "px"} 312 color="darkgrey" 313 onClick={() => handleReply(opinion)} 314 /> 315 </div> 237 316 </OpinionCardContent> 238 317 {opinion.children.map((child) => … … 243 322 ); 244 323 } 324 return null; 245 325 })} 246 326 {postForModal && ( … … 266 346 </label> 267 347 </ModalBody> 348 <p 349 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }} 350 > 351 {errorMessage} 352 </p> 268 353 <ModalFooter type="submit">РЕПЛИЦИРАЈ</ModalFooter> 269 354 </form> -
reactapp/src/Components/Styled/EntityList.style.js
r8d83180 rc68150f 18 18 export const EntityParam = styled.p` 19 19 position: absolute; 20 right: 30px;20 right: ${(props) => props.right}; 21 21 top: 10px; 22 22 font-style: italic; -
reactapp/src/Components/Styled/Modal.style.js
r8d83180 rc68150f 18 18 float: right; 19 19 font-weight: bold; 20 box-shadow: 2px 1px 10px #aaaaaa; 20 21 `; 21 22 -
reactapp/src/Components/Styled/OpinionCard.style.js
r8d83180 rc68150f 18 18 } 19 19 position: relative; 20 overflow-wrap: break-word; 20 21 `; 21 22 … … 55 56 display: block; 56 57 position: absolute; 58 height: 20px; 57 59 top: 50%; 58 60 transform: translateY(-50%); … … 60 62 transition: 0.5s; 61 63 &:hover { 62 color: ${(props) => props.color};64 opacity: 0.5; 63 65 cursor: pointer; 64 66 } -
reactapp/src/Components/SubjectsAccordion.js
r8d83180 rc68150f 27 27 <SubjectsAccordionDiv height={height} opacity={opacity}> 28 28 <EntityUl> 29 {props.content.map((item) => { 30 let totalPosts = item.threads.length; 31 return ( 32 <EntityLi key={item.subjectName} bgcolor="cornsilk"> 33 <a href={"/subject/" + item.subjectId}>{item.subjectName}</a> 34 <EntityParam> 35 {totalPosts !== 1 ? ( 36 totalPosts !== 0 ? ( 37 <span 38 style={{ 39 fontWeight: "normal", 40 opacity: totalPosts === 0 ? "0.5" : "1", 41 }} 42 > 29 {props.content && 30 props.content.map((item) => { 31 let totalPosts = item.threads.length; 32 return ( 33 <EntityLi key={item.subjectName} bgcolor="cornsilk"> 34 <a href={"/subject/" + item.subjectId}>{item.subjectName}</a> 35 <EntityParam right="30px"> 36 {totalPosts !== 1 ? ( 37 totalPosts !== 0 ? ( 43 38 <span 44 39 style={{ 45 fontWeight: " bold",40 fontWeight: "normal", 46 41 opacity: totalPosts === 0 ? "0.5" : "1", 47 42 }} 48 43 > 49 {totalPosts}50 </span>{" "}51 мислења52 </span>53 ) : (54 <span55 style={{56 fontWeight: "normal",57 opacity: totalPosts === 0 ? "0.5" : "1",58 }}59 >44 <span 45 style={{ 46 fontWeight: "bold", 47 opacity: totalPosts === 0 ? "0.5" : "1", 48 }} 49 > 50 {totalPosts} 51 </span>{" "} 52 мислења 53 </span> 54 ) : ( 60 55 <span 61 56 style={{ 62 fontWeight: " bold",57 fontWeight: "normal", 63 58 opacity: totalPosts === 0 ? "0.5" : "1", 64 59 }} 65 60 > 66 {totalPosts} 67 </span>{" "} 68 мислења 61 <span 62 style={{ 63 fontWeight: "bold", 64 opacity: totalPosts === 0 ? "0.5" : "1", 65 }} 66 > 67 {totalPosts} 68 </span>{" "} 69 мислења 70 </span> 71 ) 72 ) : ( 73 <span style={{ fontWeight: "normal" }}> 74 <span style={{ fontWeight: "bold" }}>{totalPosts}</span>{" "} 75 мислење 69 76 </span> 70 ) 71 ) : ( 72 <span style={{ fontWeight: "normal" }}> 73 <span style={{ fontWeight: "bold" }}>{totalPosts}</span>{" "} 74 мислење 75 </span> 76 )} 77 </EntityParam> 78 </EntityLi> 79 ); 80 })} 77 )} 78 </EntityParam> 79 </EntityLi> 80 ); 81 })} 81 82 </EntityUl> 82 83 </SubjectsAccordionDiv> -
reactapp/src/Components/UserHeader.js
r8d83180 rc68150f 4 4 import Logout from "./Logout"; 5 5 6 function UserHeader({ user, userLoaded }) { 7 return userLoaded ? ( 6 function UserHeader({}) { 7 const [user, setUser] = useState(null); 8 const [loadedUser, setLoadedUser] = useState(false); 9 const [fetchError, setFetchError] = useState(false); 10 11 useEffect(() => { 12 const url = `http://192.168.0.17:8080/secure/currentUser`; 13 14 const fetchUser = async () => { 15 try { 16 const response = await axios.get(url, { withCredentials: true }); 17 var cyclicGraph = await response.data; 18 var jsogStructure = JSOG.encode(cyclicGraph); 19 cyclicGraph = JSOG.decode(jsogStructure); 20 setUser(cyclicGraph); 21 setLoadedUser(true); 22 } catch (error) { 23 setFetchError(true); 24 } 25 }; 26 27 fetchUser(); 28 }, []); 29 30 return loadedUser ? ( 8 31 <div style={{ float: "left", marginTop: 20, marginLeft: 40 }}> 9 32 Најавен/а: <a href="/user_dashboard">{user.username}</a> <Logout />{" "} -
reactapp/src/Pages/Faculty.js
r8d83180 rc68150f 60 60 }, [params.facultyId]); 61 61 62 return loadedProfessors ? (62 return loadedProfessors && professors.length != 0 ? ( 63 63 entityType === 0 ? ( 64 64 <> … … 114 114 {professor.professorName} 115 115 </a> 116 <EntityParam >116 <EntityParam right="30px"> 117 117 {totalPosts !== 1 ? ( 118 118 totalPosts !== 0 ? ( … … 168 168 <> 169 169 <CurrentPageNav> 170 »{" "} 170 171 <a 171 172 href={ … … 175 176 {professors[0].faculty.university.universityName} 176 177 </a>{" "} 177 /<a href="#">{professors[0].faculty.facultyName}</a>178 » <a href="#">{professors[0].faculty.facultyName}</a> 178 179 </CurrentPageNav> 179 180 <ProfessorCard> … … 221 222 ) 222 223 ) 223 ) : !fetchError ? (224 ) : !fetchError && !loadedProfessors ? ( 224 225 <div> 225 226 <p style={{ marginTop: "140px" }}>се вчитува...</p> -
reactapp/src/Pages/Home.js
r8d83180 rc68150f 24 24 </a>{" "} 25 25 <Search /> 26 {auth && <UserHeader user={user} userLoaded={userLoaded}/>}26 {auth && <UserHeader />} 27 27 <div style={{ marginTop: "140px" }}></div> 28 28 <Outlet /> -
reactapp/src/Pages/Login.js
r8d83180 rc68150f 35 35 if (!response.request.responseURL.includes("error")) { 36 36 // ako NE redirektira na /login?error 37 Cookies.set("JSESSIONID", response.data.sessionId); 37 var in30Minutes = 1 / 48; 38 Cookies.set("JSESSIONID", response.data.sessionId, { 39 expires: in30Minutes, 40 }); 38 41 setAuth(true); 39 42 setErrMsg(""); -
reactapp/src/Pages/Professor.js
r8d83180 rc68150f 25 25 import { CurrentPageNav } from "../Components/Styled/Main.style"; 26 26 27 function Professor( user, userLoaded) {27 function Professor() { 28 28 let params = useParams(); 29 let navigate = useNavigate(); 29 30 30 let [professor, setProfessor] = useState(null);31 let [loaded, setLoaded] = useState(null);32 let [postModalDisplay, setPostModalDisplay] = useState("none"); 33 let navigate = useNavigate();31 const [professor, setProfessor] = useState(null); 32 const [loadedProfessor, setLoadedProfessor] = useState(false); 33 34 const [postModalDisplay, setPostModalDisplay] = useState("none"); 34 35 const { auth, setAuth } = useContext(AuthApi); 35 const [postTitle, setPostTitle] = useState("");36 36 const [postContent, setPostContent] = useState(""); 37 37 const [fetchError, setFetchError] = useState(false); 38 const [errorMessage, setErrorMessage] = useState(""); 38 39 39 40 useEffect(() => { 40 41 const url = `http://192.168.0.17:8080/public/professor/${params.professorId}`; 41 42 42 const fetch Data= async () => {43 const fetchProfessor = async () => { 43 44 try { 44 45 const response = await fetch(url); … … 47 48 cyclicGraph = JSOG.decode(jsogStructure); 48 49 setProfessor(cyclicGraph); 49 setLoaded (true);50 setLoadedProfessor(true); 50 51 } catch (error) { 51 52 setFetchError(true); … … 53 54 }; 54 55 55 fetch Data();56 fetchProfessor(); 56 57 }, [params.professorId]); 57 58 … … 71 72 e.preventDefault(); 72 73 73 const response = await axios( 74 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/addOpinion`, 75 { 76 method: "post", 77 data: { 78 title: postTitle, 79 content: postContent, 80 }, 81 withCredentials: true, 82 } 83 ); 84 85 window.location.reload(false); 86 }; 87 88 const handleTitleChange = (e) => { 89 setPostTitle(e.target.value); 74 if (!postContent.length < 1) { 75 const response = await axios( 76 `http://192.168.0.17:8080/secure/professor/${params.professorId}/addOpinion`, 77 { 78 method: "post", 79 data: { 80 content: postContent, 81 }, 82 withCredentials: true, 83 } 84 ); 85 setErrorMessage(""); 86 window.location.reload(false); 87 } else { 88 setErrorMessage("Полето за содржина не смее да биде празно"); 89 } 90 90 }; 91 91 … … 94 94 }; 95 95 96 if (loaded ) {96 if (loadedProfessor) { 97 97 return ( 98 98 <div> … … 120 120 </div> 121 121 </ProfessorCard> 122 <div style={{ height: "20px", marginBottom: " 30px" }}>122 <div style={{ height: "20px", marginBottom: "50px" }}> 123 123 <h3 124 124 style={{ … … 146 146 <form onSubmit={handlePostSubmit}> 147 147 <ModalBody> 148 <label htmlFor="title">149 <b>Наслов</b>:150 <ModalInput151 id="title"152 type="text"153 value={postTitle}154 onChange={handleTitleChange}155 />156 </label>157 148 <label htmlFor="content"> 158 149 <b>Содржина</b>: … … 166 157 </label> 167 158 </ModalBody> 159 <p 160 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }} 161 > 162 {errorMessage} 163 </p> 168 164 <ModalFooter type="submit">ОБЈАВИ</ModalFooter> 169 165 </form> … … 172 168 173 169 <div className="opinionTree"> 174 <OpinionTree 175 professor={professor} 176 user={user} 177 userLoaded={userLoaded} 178 /> 170 <OpinionTree professor={professor} /> 179 171 </div> 180 172 <Outlet /> -
reactapp/src/Pages/Registration.js
r8d83180 rc68150f 1 import React, { useRef, useState, useEffect } from "react";1 import React, { useRef, useState, useEffect, useContext } from "react"; 2 2 import axios from "../api/axios"; 3 3 import { … … 7 7 RequiredAsterisk, 8 8 } from "../Components/Styled/Login.style"; 9 import { Navigate } from "react-router-dom"; 10 import AuthApi from "../api/AuthApi"; 9 11 const REGISTRATION_URL = "/registration"; 10 12 11 13 const Registration = () => { 14 const { auth, setAuth } = useContext(AuthApi); 12 15 const userRef = useRef(); 13 16 const errRef = useRef(); … … 50 53 useState(0); 51 54 52 return registrationSuccessful === false ? ( 55 return auth ? ( 56 <Navigate to="/user_dashboard" /> 57 ) : registrationSuccessful === false ? ( 53 58 <div 54 59 style={{ -
reactapp/src/Pages/Subject.js
r8d83180 rc68150f 1 import React from "react"; 2 import { useParams } from "react-router-dom"; 1 import React, { useState, useEffect, useContext } from "react"; 2 import { useNavigate, useParams } from "react-router-dom"; 3 import JSOG from "jsog"; 4 import { Outlet } from "react-router-dom"; 5 import { CurrentPageNav } from "../Components/Styled/Main.style"; 6 import { 7 ProfessorCard, 8 ProfessorCardDetails, 9 ProfessorCardName, 10 ProfessorCardSeparator, 11 } from "../Components/Styled/ProfessorCard.style"; 12 import AuthApi from "../api/AuthApi"; 13 import { AddOpinionButton } from "../Components/Styled/Modal.style"; 14 import { 15 EntityLi, 16 EntityUl, 17 EntityParam, 18 } from "../Components/Styled/EntityList.style"; 19 import { 20 Modal, 21 ModalBody, 22 ModalClose, 23 ModalContent, 24 ModalFooter, 25 ModalHeader, 26 ModalInput, 27 ModalTextarea, 28 } from "../Components/Styled/Modal.style"; 29 import axios from "../api/axios"; 3 30 4 31 const Subject = () => { 5 32 let params = useParams(); 6 return <div>Subject {params.subjectId}</div>; 33 let navigate = useNavigate(); 34 35 const { auth, setAuth } = useContext(AuthApi); 36 const [subject, setSubject] = useState(null); 37 const [loaded, setLoaded] = useState(false); 38 const [fetchError, setFetchError] = useState(false); 39 var totalTopics = 0; 40 var topics = []; 41 42 const [topicModalDisplay, setTopicModalDisplay] = useState("none"); 43 const [topicTitle, setTopicTitle] = useState(""); 44 const [topicContent, setTopicContent] = useState(""); 45 const [errorMessage, setErrorMessage] = useState(""); 46 47 useEffect(() => { 48 const url = `http://192.168.0.17:8080/public/subject/${params.subjectId}`; 49 50 const fetchData = async () => { 51 try { 52 const response = await fetch(url); 53 let cyclicGraph = await response.json(); 54 let jsogStructure = JSOG.encode(cyclicGraph); 55 cyclicGraph = JSOG.decode(jsogStructure); 56 setSubject(cyclicGraph); 57 setLoaded(true); 58 } catch (error) { 59 setFetchError(true); 60 } 61 }; 62 63 fetchData(); 64 }, [params.subjectId]); 65 66 const handleAddTopicButtonClick = () => { 67 if (auth) { 68 setTopicModalDisplay("block"); 69 } else { 70 navigate("/login"); 71 } 72 }; 73 74 const handleModalCloseClick = () => { 75 setTopicModalDisplay("none"); 76 }; 77 78 const handleTopicSubmit = async (e) => { 79 e.preventDefault(); 80 81 if (!topicTitle.length < 1 && !topicContent.length < 1) { 82 const response = await axios( 83 `http://192.168.0.17:8080/secure/subject/${params.subjectId}/addThread`, 84 { 85 method: "post", 86 data: { 87 title: topicTitle, 88 content: topicContent, 89 }, 90 withCredentials: true, 91 } 92 ); 93 setErrorMessage(""); 94 window.location.reload(false); 95 } else { 96 setErrorMessage("Полињата за наслов и содржина не смеат да бидат празни"); 97 } 98 }; 99 100 const handleContentChange = (e) => { 101 setTopicContent(e.target.value); 102 }; 103 104 const handleTitleChange = (e) => { 105 setTopicTitle(e.target.value); 106 }; 107 108 return loaded ? ( 109 <> 110 <CurrentPageNav> 111 »{" "} 112 <a 113 href={ 114 "/university/" + 115 subject.studyProgramme.faculty.university.universityId 116 } 117 > 118 {subject.studyProgramme.faculty.university.universityName} 119 </a>{" "} 120 »{" "} 121 <a href={"/faculty/" + subject.studyProgramme.faculty.facultyId}> 122 {subject.studyProgramme.faculty.facultyName} 123 </a>{" "} 124 » <a href="#">{subject.subjectName}</a> 125 </CurrentPageNav> 126 <ProfessorCard> 127 <ProfessorCardName>{subject.subjectName}</ProfessorCardName> 128 <ProfessorCardSeparator /> 129 <div style={{ marginTop: "10px" }}> 130 <ProfessorCardDetails fontSize="20px"> 131 {subject.studyProgramme.studyProgrammeName} ( 132 {subject.studyProgramme.cycle} 133 {"."} 134 {"циклус"}) 135 </ProfessorCardDetails> 136 <ProfessorCardDetails fontSize="20px"> 137 {subject.studyProgramme.faculty.facultyName} 138 </ProfessorCardDetails> 139 <ProfessorCardDetails fontSize="15px"> 140 {subject.studyProgramme.faculty.university.universityName} 141 </ProfessorCardDetails> 142 </div> 143 </ProfessorCard> 144 <div style={{ height: "20px", marginBottom: "50px" }}> 145 <h3 146 style={{ 147 float: "left", 148 }} 149 > 150 {subject.threads.map((thread) => { 151 if (thread.parent === null) { 152 totalTopics++; 153 topics.push(thread); 154 } 155 })} 156 {totalTopics} {totalTopics !== 1 ? "теми" : "тема"} 157 </h3> 158 {auth && ( 159 <AddOpinionButton onClick={handleAddTopicButtonClick}> 160 Отвори тема 161 </AddOpinionButton> 162 )} 163 </div> 164 <Modal display={topicModalDisplay}> 165 <ModalContent> 166 <ModalHeader> 167 <ModalClose onClick={handleModalCloseClick}>×</ModalClose> 168 <h3 style={{ marginTop: "5px" }}> 169 Тема во врска со {subject.subjectName} 170 </h3> 171 </ModalHeader> 172 <form onSubmit={handleTopicSubmit}> 173 <ModalBody> 174 <label htmlFor="title"> 175 <b>Наслов</b>: 176 <ModalInput 177 id="title" 178 value={topicTitle} 179 onChange={handleTitleChange} 180 /> 181 </label> 182 <label htmlFor="content"> 183 <b>Содржина</b>: 184 <ModalTextarea 185 id="content" 186 rows="8" 187 cols="100" 188 value={topicContent} 189 onChange={handleContentChange} 190 /> 191 </label> 192 </ModalBody> 193 <p style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}> 194 {errorMessage} 195 </p> 196 <ModalFooter type="submit">ОБЈАВИ</ModalFooter> 197 </form> 198 </ModalContent> 199 </Modal> 200 <div key={subject.subjectId}> 201 {topics.map((topic) => { 202 var numReplies = topic.children.length; 203 return ( 204 <EntityUl key={topic.postId}> 205 <EntityLi bgcolor="cornsilk"> 206 <a href={"/topic/" + topic.postId}>{topic.title}</a> 207 <EntityParam right="30px"> 208 <span style={{ fontWeight: "normal" }}> 209 отворил:{" "} 210 <a href={"/user/" + topic.author.id}> 211 {topic.author.username} 212 </a> 213 </span> 214 <span style={{ fontStyle: "normal" }}>,</span>{" "} 215 {numReplies !== 1 ? ( 216 numReplies !== 0 ? ( 217 <span 218 style={{ 219 fontWeight: "normal", 220 opacity: numReplies === 0 ? "0.5" : "1", 221 }} 222 > 223 <span 224 style={{ 225 fontWeight: "bold", 226 opacity: numReplies === 0 ? "0.5" : "1", 227 }} 228 > 229 {numReplies} 230 </span>{" "} 231 реплики 232 </span> 233 ) : ( 234 <span 235 style={{ 236 fontWeight: "normal", 237 opacity: numReplies === 0 ? "0.5" : "1", 238 }} 239 > 240 <span 241 style={{ 242 fontWeight: "bold", 243 opacity: numReplies === 0 ? "0.5" : "1", 244 }} 245 > 246 {numReplies} 247 </span>{" "} 248 реплики 249 </span> 250 ) 251 ) : ( 252 <span style={{ fontWeight: "normal" }}> 253 <span style={{ fontWeight: "bold" }}>{numReplies}</span>{" "} 254 реплика 255 </span> 256 )} 257 </EntityParam> 258 </EntityLi> 259 </EntityUl> 260 ); 261 })} 262 </div> 263 </> 264 ) : !fetchError ? ( 265 <div> 266 <p style={{ marginTop: "140px" }}>се вчитува...</p> 267 <Outlet /> 268 </div> 269 ) : ( 270 <div style={{ marginTop: "140px" }}> 271 <h1 style={{ textAlign: "center" }}>Страницата не е пронајдена.</h1> 272 </div> 273 ); 7 274 }; 8 275 -
reactapp/src/Pages/University.js
r8d83180 rc68150f 18 18 let params = useParams(); 19 19 const [loaded, setLoaded] = useState(false); 20 const [faculties, setFaculties] = useState( false);20 const [faculties, setFaculties] = useState(null); 21 21 const [fetchError, setFetchError] = useState(false); 22 22 … … 39 39 }, [params.universityId]); 40 40 41 return loaded ? (41 return loaded && !fetchError && faculties.length !== 0 ? ( 42 42 <> 43 43 <CurrentPageNav> … … 74 74 {faculty.facultyName} 75 75 </a> 76 <EntityParam >76 <EntityParam right="30px"> 77 77 {totalSections}{" "} 78 78 {totalSections !== 1 ? ( … … 103 103 </div> 104 104 </> 105 ) : !fetchError ? (105 ) : !fetchError && !loaded ? ( 106 106 <div> 107 107 <p style={{ marginTop: "140px" }}>се вчитува...</p> -
reactapp/src/Pages/UserDashboard.js
r8d83180 rc68150f 1 import React, { useEffect } from "react";1 import React, { useEffect, useState, useContext } from "react"; 2 2 import { 3 3 OpinionCard, 4 4 OpinionCardContent, 5 5 OpinionCardContentTime, 6 OpinionCardContentTitle,7 6 } from "../Components/Styled/OpinionCard.style"; 8 7 import { … … 11 10 } from "../Components/Styled/UserDetails.style"; 12 11 import { dateConverter } from "../Util/dateConverter"; 12 import axios from "../api/axios"; 13 import JSOG from "jsog"; 14 import AuthApi from "../api/AuthApi"; 13 15 14 function UserDashboard({ user, userLoaded }) { 16 function UserDashboard() { 17 const { auth, setAuth } = useContext(AuthApi); 18 19 const [user, setUser] = useState(null); 20 const [loadedUser, setLoadedUser] = useState(false); 21 const [fetchError, setFetchError] = useState(false); 22 15 23 useEffect(() => { 16 const timer = setTimeout(() => { 17 if (user === null) window.location.reload(false); 18 }, 3000); 19 return () => clearTimeout(timer); 24 const url = `http://192.168.0.17:8080/secure/currentUser`; 25 26 const fetchUser = async () => { 27 try { 28 const response = await axios.get(url, { withCredentials: true }); 29 var cyclicGraph = await response.data; 30 var jsogStructure = JSOG.encode(cyclicGraph); 31 cyclicGraph = JSOG.decode(jsogStructure); 32 setUser(cyclicGraph); 33 setLoadedUser(true); 34 } catch (error) { 35 setFetchError(true); 36 } 37 }; 38 39 if (auth) fetchUser(); 20 40 }, []); 21 41 22 return userLoaded ? ( 42 // useEffect(() => { 43 // const timer = setTimeout(() => { 44 // if (user === null) window.location.reload(false); <---- :-) 45 // }, 3000); 46 // return () => clearTimeout(timer); 47 // }, []); 48 49 function findParentThread(post) { 50 if (post.parent === null) return post; 51 return findParentThread(post.parent); 52 } 53 54 return loadedUser ? ( 23 55 <> 24 56 <h3>Кориснички податоци:</h3> … … 49 81 <OpinionCard> 50 82 <OpinionCardContent> 51 <p> 52 Во дискусија за{" "} 53 <a href={"/professor/" + post.targetProfessor.professorId}> 54 {post.targetProfessor.professorName} 55 </a> 83 <p style={{ fontStyle: "italic", marginBottom: "10px" }}> 84 во дискусија за{" "} 85 {post.targetProfessor !== undefined ? ( 86 <a href={"/professor/" + post.targetProfessor.professorId}> 87 {post.targetProfessor.professorName} 88 </a> 89 ) : ( 90 <a 91 href={ 92 post.parent === null 93 ? "/topic/" + post.postId 94 : "/topic/" + findParentThread(post).postId 95 } 96 > 97 {post.targetSubject.subjectName} 98 </a> 99 )} 56 100 </p> 57 <OpinionCardContentTitle>{post.title}</OpinionCardContentTitle> 58 <p>{post.content}</p> 101 <p style={{ marginBottom: "10px" }}>{post.content}</p> 59 102 <OpinionCardContentTime> 60 103 {dateConverter(
Note:
See TracChangeset
for help on using the changeset viewer.