Changeset c68150f for reactapp/src


Ignore:
Timestamp:
10/27/22 17:35:03 (2 years ago)
Author:
unknown <mlviktor23@…>
Branches:
main
Children:
3b6962d
Parents:
8d83180
Message:

left: moderation, oAuth, messaging

Location:
reactapp/src
Files:
1 added
15 edited

Legend:

Unmodified
Added
Removed
  • reactapp/src/App.js

    r8d83180 rc68150f  
    1212import AuthApi from "./api/AuthApi";
    1313import Cookies from "js-cookie";
    14 import axios from "./api/axios";
    15 import JSOG from "jsog";
    1614import NotFound from "./Pages/NotFound";
     15import Topic from "./Pages/Topic";
    1716
    1817export default function App() {
    1918  const [auth, setAuth] = useState(false);
    20   const [user, setUser] = useState(null);
    21   const [userLoaded, setUserLoaded] = useState(false);
    2219  const variableAuth = useMemo(() => ({ auth, setAuth }), [auth]);
    2320  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   };
    4021
    4122  const readCookie = async () => {
     
    4324    if (session) {
    4425      setAuth(true);
    45       fetchUser();
    4626    } else {
    4727      setAuth(false);
     
    5131
    5232  useEffect(() => {
     33    document.title = "profesori.mk";
    5334    readCookie();
    5435  }, []);
     
    6950      <BrowserRouter>
    7051        <Routes>
    71           <Route
    72             path="/"
    73             element={<Home user={user} userLoaded={userLoaded} />}
    74           >
     52          <Route path="/" element={<Home />}>
    7553            <Route path="login" element={<Login />}></Route>
    7654            <Route path="registration" element={<Registration />}></Route>
    7755            <Route path="professor">
    78               <Route
    79                 path=":professorId"
    80                 element={<Professor user={user} userLoaded={userLoaded} />}
    81               />
     56              <Route path=":professorId" element={<Professor />} />
    8257            </Route>
    8358            <Route path="university/:universityId" element={<University />} />
    8459            <Route path="faculty/:facultyId" element={<Faculty />} />
    8560            <Route path="subject/:subjectId" element={<Subject />} />
     61            <Route path="topic/:topicId" element={<Topic />} />
    8662            <Route path="search" element={<SearchResults />}></Route>
    8763            <Route
    8864              path="user_dashboard"
    8965              element={
    90                 <ProtectedRoute auth={auth}>
    91                   {<UserDashboard user={user} userLoaded={userLoaded} />}
    92                 </ProtectedRoute>
     66                <ProtectedRoute auth={auth}>{<UserDashboard />}</ProtectedRoute>
    9367              }
    9468            ></Route>
  • reactapp/src/Components/OpinionTree.js

    r8d83180 rc68150f  
    33  OpinionCardContent,
    44  OpinionCardContentTime,
    5   OpinionCardContentTitle,
    65  OpinionReplyCard,
    76  OpinionReplyCardContent,
     
    1413import AuthApi from "../api/AuthApi";
    1514import { useNavigate } from "react-router-dom";
    16 import { useContext, useState } from "react";
     15import { useContext, useState, useEffect } from "react";
     16import JSOG from "jsog";
    1717import {
    1818  Modal,
     
    2626import axios from "../api/axios";
    2727
    28 function OpinionTree({ professor, user, userLoaded }) {
     28function OpinionTree({ professor }) {
    2929  var renderedOpinionIds = [];
    3030  var postCount; // za da ne go pokazuva ispod postot
    3131
     32  const { auth, setAuth } = useContext(AuthApi);
    3233  let navigate = useNavigate();
    33   const { auth, setAuth } = useContext(AuthApi);
    34 
    35   let [replyModalDisplay, setReplyModalDisplay] = useState("none");
     34
     35  const [replyModalDisplay, setReplyModalDisplay] = useState("none");
    3636  const [replyContent, setReplyContent] = useState("");
    37 
    3837  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  }, []);
    3961
    4062  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      }
    5680    } else {
    57       return;
     81      navigate("/login");
    5882    }
    5983  };
    6084
    6185  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      }
    78104    } else {
    79       return;
     105      navigate("/login");
    80106    }
    81107  };
     
    101127    e.preventDefault();
    102128
    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    }
    116145  };
    117146
     
    123152        <OpinionReplyCard indent={replyIndent + "px"}>
    124153          <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}
    128157            </p>
    129             <p>{child.content}</p>
     158            <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
     159              {child.content}
     160            </p>
    130161            <OpinionReplyCardContentTime>
    131162              {dateConverter(
     
    133164              )}
    134165            </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                      )
    142183                      ? "greenyellow"
    143184                      : "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                      )
    153202                      ? "indianred"
    154203                      : "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>
    167220          </OpinionReplyCardContent>
    168221          {child.children.map((childOfChild) =>
     
    187240              <OpinionCard>
    188241                <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                    напишал
    191247                  </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>
    196251                  <OpinionCardContentTime>
    197252                    {dateConverter(
     
    199254                    )}
    200255                  </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                            )
    208275                            ? "greenyellow"
    209276                            : "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                            )
    221295                            ? "indianred"
    222296                            : "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>
    237316                </OpinionCardContent>
    238317                {opinion.children.map((child) =>
     
    243322          );
    244323        }
     324        return null;
    245325      })}
    246326      {postForModal && (
     
    266346                </label>
    267347              </ModalBody>
     348              <p
     349                style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
     350              >
     351                {errorMessage}
     352              </p>
    268353              <ModalFooter type="submit">РЕПЛИЦИРАЈ</ModalFooter>
    269354            </form>
  • reactapp/src/Components/Styled/EntityList.style.js

    r8d83180 rc68150f  
    1818export const EntityParam = styled.p`
    1919  position: absolute;
    20   right: 30px;
     20  right: ${(props) => props.right};
    2121  top: 10px;
    2222  font-style: italic;
  • reactapp/src/Components/Styled/Modal.style.js

    r8d83180 rc68150f  
    1818  float: right;
    1919  font-weight: bold;
     20  box-shadow: 2px 1px 10px #aaaaaa;
    2021`;
    2122
  • reactapp/src/Components/Styled/OpinionCard.style.js

    r8d83180 rc68150f  
    1818  }
    1919  position: relative;
     20  overflow-wrap: break-word;
    2021`;
    2122
     
    5556  display: block;
    5657  position: absolute;
     58  height: 20px;
    5759  top: 50%;
    5860  transform: translateY(-50%);
     
    6062  transition: 0.5s;
    6163  &:hover {
    62     color: ${(props) => props.color};
     64    opacity: 0.5;
    6365    cursor: pointer;
    6466  }
  • reactapp/src/Components/SubjectsAccordion.js

    r8d83180 rc68150f  
    2727      <SubjectsAccordionDiv height={height} opacity={opacity}>
    2828        <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 ? (
    4338                        <span
    4439                          style={{
    45                             fontWeight: "bold",
     40                            fontWeight: "normal",
    4641                            opacity: totalPosts === 0 ? "0.5" : "1",
    4742                          }}
    4843                        >
    49                           {totalPosts}
    50                         </span>{" "}
    51                         мислења
    52                       </span>
    53                     ) : (
    54                       <span
    55                         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                      ) : (
    6055                        <span
    6156                          style={{
    62                             fontWeight: "bold",
     57                            fontWeight: "normal",
    6358                            opacity: totalPosts === 0 ? "0.5" : "1",
    6459                          }}
    6560                        >
    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                        мислење
    6976                      </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            })}
    8182        </EntityUl>
    8283      </SubjectsAccordionDiv>
  • reactapp/src/Components/UserHeader.js

    r8d83180 rc68150f  
    44import Logout from "./Logout";
    55
    6 function UserHeader({ user, userLoaded }) {
    7   return userLoaded ? (
     6function 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 ? (
    831    <div style={{ float: "left", marginTop: 20, marginLeft: 40 }}>
    932      Најавен/а: <a href="/user_dashboard">{user.username}</a> <Logout />{" "}
  • reactapp/src/Pages/Faculty.js

    r8d83180 rc68150f  
    6060  }, [params.facultyId]);
    6161
    62   return loadedProfessors ? (
     62  return loadedProfessors && professors.length != 0 ? (
    6363    entityType === 0 ? (
    6464      <>
     
    114114                    {professor.professorName}
    115115                  </a>
    116                   <EntityParam>
     116                  <EntityParam right="30px">
    117117                    {totalPosts !== 1 ? (
    118118                      totalPosts !== 0 ? (
     
    168168        <>
    169169          <CurrentPageNav>
     170            &#187;{" "}
    170171            <a
    171172              href={
     
    175176              {professors[0].faculty.university.universityName}
    176177            </a>{" "}
    177             / <a href="#">{professors[0].faculty.facultyName}</a>
     178            &#187; <a href="#">{professors[0].faculty.facultyName}</a>
    178179          </CurrentPageNav>
    179180          <ProfessorCard>
     
    221222      )
    222223    )
    223   ) : !fetchError ? (
     224  ) : !fetchError && !loadedProfessors ? (
    224225    <div>
    225226      <p style={{ marginTop: "140px" }}>се вчитува...</p>
  • reactapp/src/Pages/Home.js

    r8d83180 rc68150f  
    2424      </a>{" "}
    2525      <Search />
    26       {auth && <UserHeader user={user} userLoaded={userLoaded} />}
     26      {auth && <UserHeader />}
    2727      <div style={{ marginTop: "140px" }}></div>
    2828      <Outlet />
  • reactapp/src/Pages/Login.js

    r8d83180 rc68150f  
    3535    if (!response.request.responseURL.includes("error")) {
    3636      // 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      });
    3841      setAuth(true);
    3942      setErrMsg("");
  • reactapp/src/Pages/Professor.js

    r8d83180 rc68150f  
    2525import { CurrentPageNav } from "../Components/Styled/Main.style";
    2626
    27 function Professor(user, userLoaded) {
     27function Professor() {
    2828  let params = useParams();
     29  let navigate = useNavigate();
    2930
    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");
    3435  const { auth, setAuth } = useContext(AuthApi);
    35   const [postTitle, setPostTitle] = useState("");
    3636  const [postContent, setPostContent] = useState("");
    3737  const [fetchError, setFetchError] = useState(false);
     38  const [errorMessage, setErrorMessage] = useState("");
    3839
    3940  useEffect(() => {
    4041    const url = `http://192.168.0.17:8080/public/professor/${params.professorId}`;
    4142
    42     const fetchData = async () => {
     43    const fetchProfessor = async () => {
    4344      try {
    4445        const response = await fetch(url);
     
    4748        cyclicGraph = JSOG.decode(jsogStructure);
    4849        setProfessor(cyclicGraph);
    49         setLoaded(true);
     50        setLoadedProfessor(true);
    5051      } catch (error) {
    5152        setFetchError(true);
     
    5354    };
    5455
    55     fetchData();
     56    fetchProfessor();
    5657  }, [params.professorId]);
    5758
     
    7172    e.preventDefault();
    7273
    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    }
    9090  };
    9191
     
    9494  };
    9595
    96   if (loaded) {
     96  if (loadedProfessor) {
    9797    return (
    9898      <div>
     
    120120          </div>
    121121        </ProfessorCard>
    122         <div style={{ height: "20px", marginBottom: "30px" }}>
     122        <div style={{ height: "20px", marginBottom: "50px" }}>
    123123          <h3
    124124            style={{
     
    146146            <form onSubmit={handlePostSubmit}>
    147147              <ModalBody>
    148                 <label htmlFor="title">
    149                   <b>Наслов</b>:
    150                   <ModalInput
    151                     id="title"
    152                     type="text"
    153                     value={postTitle}
    154                     onChange={handleTitleChange}
    155                   />
    156                 </label>
    157148                <label htmlFor="content">
    158149                  <b>Содржина</b>:
     
    166157                </label>
    167158              </ModalBody>
     159              <p
     160                style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
     161              >
     162                {errorMessage}
     163              </p>
    168164              <ModalFooter type="submit">ОБЈАВИ</ModalFooter>
    169165            </form>
     
    172168
    173169        <div className="opinionTree">
    174           <OpinionTree
    175             professor={professor}
    176             user={user}
    177             userLoaded={userLoaded}
    178           />
     170          <OpinionTree professor={professor} />
    179171        </div>
    180172        <Outlet />
  • reactapp/src/Pages/Registration.js

    r8d83180 rc68150f  
    1 import React, { useRef, useState, useEffect } from "react";
     1import React, { useRef, useState, useEffect, useContext } from "react";
    22import axios from "../api/axios";
    33import {
     
    77  RequiredAsterisk,
    88} from "../Components/Styled/Login.style";
     9import { Navigate } from "react-router-dom";
     10import AuthApi from "../api/AuthApi";
    911const REGISTRATION_URL = "/registration";
    1012
    1113const Registration = () => {
     14  const { auth, setAuth } = useContext(AuthApi);
    1215  const userRef = useRef();
    1316  const errRef = useRef();
     
    5053    useState(0);
    5154
    52   return registrationSuccessful === false ? (
     55  return auth ? (
     56    <Navigate to="/user_dashboard" />
     57  ) : registrationSuccessful === false ? (
    5358    <div
    5459      style={{
  • reactapp/src/Pages/Subject.js

    r8d83180 rc68150f  
    1 import React from "react";
    2 import { useParams } from "react-router-dom";
     1import React, { useState, useEffect, useContext } from "react";
     2import { useNavigate, useParams } from "react-router-dom";
     3import JSOG from "jsog";
     4import { Outlet } from "react-router-dom";
     5import { CurrentPageNav } from "../Components/Styled/Main.style";
     6import {
     7  ProfessorCard,
     8  ProfessorCardDetails,
     9  ProfessorCardName,
     10  ProfessorCardSeparator,
     11} from "../Components/Styled/ProfessorCard.style";
     12import AuthApi from "../api/AuthApi";
     13import { AddOpinionButton } from "../Components/Styled/Modal.style";
     14import {
     15  EntityLi,
     16  EntityUl,
     17  EntityParam,
     18} from "../Components/Styled/EntityList.style";
     19import {
     20  Modal,
     21  ModalBody,
     22  ModalClose,
     23  ModalContent,
     24  ModalFooter,
     25  ModalHeader,
     26  ModalInput,
     27  ModalTextarea,
     28} from "../Components/Styled/Modal.style";
     29import axios from "../api/axios";
    330
    431const Subject = () => {
    532  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        &#187;{" "}
     112        <a
     113          href={
     114            "/university/" +
     115            subject.studyProgramme.faculty.university.universityId
     116          }
     117        >
     118          {subject.studyProgramme.faculty.university.universityName}
     119        </a>{" "}
     120        &#187;{" "}
     121        <a href={"/faculty/" + subject.studyProgramme.faculty.facultyId}>
     122          {subject.studyProgramme.faculty.facultyName}
     123        </a>{" "}
     124        &#187; <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}>&times;</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  );
    7274};
    8275
  • reactapp/src/Pages/University.js

    r8d83180 rc68150f  
    1818  let params = useParams();
    1919  const [loaded, setLoaded] = useState(false);
    20   const [faculties, setFaculties] = useState(false);
     20  const [faculties, setFaculties] = useState(null);
    2121  const [fetchError, setFetchError] = useState(false);
    2222
     
    3939  }, [params.universityId]);
    4040
    41   return loaded ? (
     41  return loaded && !fetchError && faculties.length !== 0 ? (
    4242    <>
    4343      <CurrentPageNav>
     
    7474                  {faculty.facultyName}
    7575                </a>
    76                 <EntityParam>
     76                <EntityParam right="30px">
    7777                  {totalSections}{" "}
    7878                  {totalSections !== 1 ? (
     
    103103      </div>
    104104    </>
    105   ) : !fetchError ? (
     105  ) : !fetchError && !loaded ? (
    106106    <div>
    107107      <p style={{ marginTop: "140px" }}>се вчитува...</p>
  • reactapp/src/Pages/UserDashboard.js

    r8d83180 rc68150f  
    1 import React, { useEffect } from "react";
     1import React, { useEffect, useState, useContext } from "react";
    22import {
    33  OpinionCard,
    44  OpinionCardContent,
    55  OpinionCardContentTime,
    6   OpinionCardContentTitle,
    76} from "../Components/Styled/OpinionCard.style";
    87import {
     
    1110} from "../Components/Styled/UserDetails.style";
    1211import { dateConverter } from "../Util/dateConverter";
     12import axios from "../api/axios";
     13import JSOG from "jsog";
     14import AuthApi from "../api/AuthApi";
    1315
    14 function UserDashboard({ user, userLoaded }) {
     16function 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
    1523  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();
    2040  }, []);
    2141
    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 ? (
    2355    <>
    2456      <h3>Кориснички податоци:</h3>
     
    4981            <OpinionCard>
    5082              <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                  )}
    56100                </p>
    57                 <OpinionCardContentTitle>{post.title}</OpinionCardContentTitle>
    58                 <p>{post.content}</p>
     101                <p style={{ marginBottom: "10px" }}>{post.content}</p>
    59102                <OpinionCardContentTime>
    60103                  {dateConverter(
Note: See TracChangeset for help on using the changeset viewer.