From 2362e6122faa6766f0f560532492923fbf471505 Mon Sep 17 00:00:00 2001 From: henryanandsr <13521004@std.stei.itb.ac.id> Date: Thu, 16 Nov 2023 19:07:06 +0700 Subject: [PATCH] fix : jwt auth --- src/App.css | 3 +- src/App.tsx | 17 +- src/api/axios.tsx | 1 - src/components/Acceptance/Acceptance.tsx | 233 +++++++++--------- src/components/Assignment/AssignmentCards.tsx | 66 ++--- .../Assignment/AssignmentDetails.tsx | 37 +-- .../Assignment/CreateAssignmentModal.tsx | 5 +- .../Assignment/DeleteAssignmentDialog.tsx | 5 +- .../Assignment/EditAssignmentModal.tsx | 6 +- src/components/Assignment/Submission.tsx | 104 ++++---- .../Dashboard/UniversityDashboard.tsx | 7 +- src/components/Home/Home.tsx | 4 +- src/components/Home/UniversityHome.tsx | 6 +- src/components/Login/Login.tsx | 26 +- src/components/Register/RegisterOrg.tsx | 108 ++++---- .../Register/RegisterUniversity.tsx | 104 ++++---- src/components/Report/Report.tsx | 22 +- src/components/Scholarships/Scholarships.tsx | 22 +- src/components/Sidebar/MobileNav.tsx | 4 +- src/components/Sidebar/SidebarContent.tsx | 4 +- src/hooks/axiosPrivate.tsx | 57 ++--- src/hooks/useRefreshToken.tsx | 1 + src/utils/RequireAuth.tsx | 62 +++-- src/utils/auth.tsx | 2 +- 24 files changed, 457 insertions(+), 449 deletions(-) diff --git a/src/App.css b/src/App.css index 74b5e05..dcf6d74 100644 --- a/src/App.css +++ b/src/App.css @@ -32,7 +32,8 @@ from { transform: rotate(0deg); } + to { transform: rotate(360deg); } -} +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index b1136c9..89a31b4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import "./App.css" import { BrowserRouter as Router, Routes, Route } from "react-router-dom" import { ChakraProvider, extendTheme } from "@chakra-ui/react" import Login from "./components/Login/Login" -import RegisterOrg from "./components/Register/RegisterOrg" +import RegisterOrg from "./components/Register/RegisterOrg" import RegisterUni from "./components/Register/RegisterUniversity" import Home from "./components/Home/Home" import Unauthorized from "./components/Error/Unauthorized" @@ -17,10 +17,10 @@ import useRefreshToken from "./hooks/useRefreshToken" import { useEffect, useState } from "react" import { OrganizationDashboard } from "./components/Dashboard/OrganizationDashboard" import { UniversityDashboard } from "./components/Dashboard/UniversityDashboard" -import { handleGetInfo } from "./utils/auth" import UniversityHome from "./components/Home/UniversityHome" import Acceptance from "./components/Acceptance/Acceptance" import { Submissions } from "./components/Assignment/Submission" +import { handleGetInfo } from "./utils/auth" const ROLES = { Organization: "organization", @@ -42,7 +42,8 @@ function App() { }) const getInfo = async () => { - try { + try { + refresh(); const response = await handleGetInfo() setUserInfo({ user_id: response?.data.user_id, @@ -54,10 +55,12 @@ function App() { console.log(error) } } + useEffect(() => { + }) useEffect(() => { getInfo() - }, []) + }, [userInfo.role]) const activeLabelStyles = { transform: "scale(0.85) translateY(-24px)" }; @@ -92,7 +95,7 @@ function App() { } } } - }); + }); return ( <ChakraProvider theme={theme}> <Router> @@ -101,7 +104,7 @@ function App() { {/* Public Routes */} <Route path="login" element={<Login />} /> <Route path="register-org" element={<RegisterOrg />} /> - <Route path="register-uni" element={<RegisterUni/>} /> + <Route path="register-uni" element={<RegisterUni />} /> <Route path="unauthorized" element={<Unauthorized />} /> {/* Protected Routes */} @@ -202,7 +205,7 @@ function App() { } /> </Route> - + <Route element={<RequireAuth allowedRoles={[ROLES.Organization]} />} > diff --git a/src/api/axios.tsx b/src/api/axios.tsx index f14a9ba..66f88f3 100644 --- a/src/api/axios.tsx +++ b/src/api/axios.tsx @@ -7,5 +7,4 @@ export default axios.create({ export const axiosPrivate = axios.create({ baseURL: "http://localhost:5001", headers: { "Content-Type": "application/json" }, - withCredentials: true }) diff --git a/src/components/Acceptance/Acceptance.tsx b/src/components/Acceptance/Acceptance.tsx index 7707e50..5671f94 100644 --- a/src/components/Acceptance/Acceptance.tsx +++ b/src/components/Acceptance/Acceptance.tsx @@ -1,36 +1,37 @@ import { useEffect, useState } from "react"; import { - Button, - Icon, - InputGroup, - InputLeftElement, - Input, - Box, - Heading, - Stack, - TableContainer, - Table, - Thead, - Tr, - Th, - Tbody, - Td, - Text, - useToast, - useDisclosure, - Modal, - ModalOverlay, - ModalContent, - ModalHeader, - ModalCloseButton, - ModalBody, - ModalFooter, + Button, + Icon, + InputGroup, + InputLeftElement, + Input, + Box, + Heading, + Stack, + TableContainer, + Table, + Thead, + Tr, + Th, + Tbody, + Td, + Text, + useToast, + useDisclosure, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, } from "@chakra-ui/react"; import { Search2Icon, CheckIcon, CloseIcon } from "@chakra-ui/icons"; import { useParams } from "react-router-dom"; -import axios from "axios"; +import useAxiosPrivate from "../../hooks/axiosPrivate"; const Acceptance = () => { + const axiosInstance = useAxiosPrivate(); const { scholarshipid } = useParams() const [sid, setSID] = useState(Number(scholarshipid)) const [students, setStudents] = useState<JSX.Element[]>([]) @@ -39,14 +40,14 @@ const Acceptance = () => { /* Get all applicant */ const fetchApplicant = async () => { - const urlApplicant = new URL(process.env.REACT_APP_API_URL + "/api/scholarship/user/" + sid) - const applicantsResponse = await axios.get(urlApplicant.toString()) + const urlApplicant = new URL(process.env.REACT_APP_API_URL + "/api/scholarship/user/" + sid) + const applicantsResponse = await axiosInstance.get(urlApplicant.toString()) const applicants = await applicantsResponse.data.data.scholarships /* Fetch the names of the students */ const studentPromises = applicants.map(async (applicant: any) => { const studentURL = new URL(process.env.REACT_APP_API_URL + "/api/user/" + applicant.user_id_student) - const studentResponse = await axios.get(studentURL.toString()) + const studentResponse = await axiosInstance.get(studentURL.toString()) return { user_id: applicant.user_id_student, name: studentResponse.data.data.user.name, @@ -87,10 +88,10 @@ const Acceptance = () => { </Button> </> ) : ( - <Text color = {String(student.status) === "accepted" ? "green": "red"}> + <Text color={String(student.status) === "accepted" ? "green" : "red"}> {String(student.status) === "accepted" ? "Accepted" : "Rejected"} </Text> - + )} </Td> </Tr> @@ -116,17 +117,17 @@ const Acceptance = () => { const handleAcceptConfirm = () => { const url = new URL(process.env.REACT_APP_API_URL + `/api/scholarship/acceptance/${sid}`) console.log(url.toString()) - axios.post(url.toString(), { + axiosInstance.post(url.toString(), { "status": "accepted", "user_id": Number(selectedID) }) - .then(function (response) { - console.log(response); - }) - .catch(function (error) { - console.log(error); - }); - + .then(function (response) { + console.log(response); + }) + .catch(function (error) { + console.log(error); + }); + toast({ title: "Acceptance set", description: "This applicant has been accepted", @@ -136,19 +137,19 @@ const Acceptance = () => { }); onAcceptModalClose(); }; - + const handleRejectConfirm = () => { const url = new URL(process.env.REACT_APP_API_URL + `/api/scholarship/acceptance/${sid}`) - axios.post(url.toString(), { + axiosInstance.post(url.toString(), { "status": "rejected", "user_id": Number(selectedID) }) - .then(function (response) { - console.log(response); - }) - .catch(function (error) { - console.log(error); - }); + .then(function (response) { + console.log(response); + }) + .catch(function (error) { + console.log(error); + }); toast({ title: "Rejection set", @@ -159,81 +160,81 @@ const Acceptance = () => { }); onRejectModalClose(); }; - + useEffect(() => { fetchApplicant() }, [sid, search]); return ( <Box> - <Heading>Scholarship Acceptance</Heading> - <Stack> - {/* Search Bar */} - <InputGroup borderRadius={10} size="sm"> - <InputLeftElement pointerEvents="none" children={<Search2Icon color="gray.600" />} /> - <Input type="text" borderRadius={10} placeholder="Search Name or Status..." border="1px solid #949494" - onChange={(e) => {setSearch(e.target.value)}} - /> - </InputGroup> - </Stack> - <Box> - <TableContainer> - <Table variant="simple"> - <Thead> - <Tr> - <Th>Student Name</Th> - <Th>Email</Th> - <Th>Status</Th> - </Tr> - </Thead> - <Tbody> - {students} - </Tbody> - </Table> - </TableContainer> - </Box> - - {/* Accept Confirmation Modal */} - <Modal isOpen={acceptModalIsOpen} onClose={onAcceptModalClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader>Confirm Acceptance</ModalHeader> - <ModalCloseButton /> - <ModalBody> - Are you sure you want to accept this applicant? - </ModalBody> - <ModalFooter> - <Button colorScheme="green" mr={3} onClick={handleAcceptConfirm}> - Confirm - </Button> - <Button variant="ghost" onClick={onAcceptModalClose}> - Cancel - </Button> - </ModalFooter> - </ModalContent> - </Modal> - - {/* Reject Confirmation Modal */} - <Modal isOpen={rejectModalIsOpen} onClose={onRejectModalClose}> - <ModalOverlay /> - <ModalContent> - <ModalHeader>Confirm Rejection</ModalHeader> - <ModalCloseButton /> - <ModalBody> - Are you sure you want to reject this applicant? - </ModalBody> - <ModalFooter> - <Button colorScheme="red" mr={3} onClick={handleRejectConfirm}> - Confirm - </Button> - <Button variant="ghost" onClick={onRejectModalClose}> - Cancel - </Button> - </ModalFooter> - </ModalContent> - </Modal> + <Heading>Scholarship Acceptance</Heading> + <Stack> + {/* Search Bar */} + <InputGroup borderRadius={10} size="sm"> + <InputLeftElement pointerEvents="none" children={<Search2Icon color="gray.600" />} /> + <Input type="text" borderRadius={10} placeholder="Search Name or Status..." border="1px solid #949494" + onChange={(e) => { setSearch(e.target.value) }} + /> + </InputGroup> + </Stack> + <Box> + <TableContainer> + <Table variant="simple"> + <Thead> + <Tr> + <Th>Student Name</Th> + <Th>Email</Th> + <Th>Status</Th> + </Tr> + </Thead> + <Tbody> + {students} + </Tbody> + </Table> + </TableContainer> + </Box> + + {/* Accept Confirmation Modal */} + <Modal isOpen={acceptModalIsOpen} onClose={onAcceptModalClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader>Confirm Acceptance</ModalHeader> + <ModalCloseButton /> + <ModalBody> + Are you sure you want to accept this applicant? + </ModalBody> + <ModalFooter> + <Button colorScheme="green" mr={3} onClick={handleAcceptConfirm}> + Confirm + </Button> + <Button variant="ghost" onClick={onAcceptModalClose}> + Cancel + </Button> + </ModalFooter> + </ModalContent> + </Modal> + + {/* Reject Confirmation Modal */} + <Modal isOpen={rejectModalIsOpen} onClose={onRejectModalClose}> + <ModalOverlay /> + <ModalContent> + <ModalHeader>Confirm Rejection</ModalHeader> + <ModalCloseButton /> + <ModalBody> + Are you sure you want to reject this applicant? + </ModalBody> + <ModalFooter> + <Button colorScheme="red" mr={3} onClick={handleRejectConfirm}> + Confirm + </Button> + <Button variant="ghost" onClick={onRejectModalClose}> + Cancel + </Button> + </ModalFooter> + </ModalContent> + </Modal> </Box> ); - }; +}; - export default Acceptance; +export default Acceptance; diff --git a/src/components/Assignment/AssignmentCards.tsx b/src/components/Assignment/AssignmentCards.tsx index a0b4071..d60c50e 100644 --- a/src/components/Assignment/AssignmentCards.tsx +++ b/src/components/Assignment/AssignmentCards.tsx @@ -16,6 +16,7 @@ import { Link } from "react-router-dom" import { DeleteAssignmentDialog } from "./DeleteAssignmentDialog" import { EditAssignmentModal } from "./EditAssignmentModal" import axios from "axios" +import useAxiosPrivate from "../../hooks/axiosPrivate" export interface AssignmentCardsProps { index: Number @@ -27,34 +28,6 @@ export interface AssignmentCardsProps { onEditSuccess: () => void } -const useFetchFile = (sid : any, aid : any) => { - const [submissionFile, setSubmissionFile] = useState([]) - const FILE_URL = process.env.REACT_APP_API_URL + "/api/files/scholarship/" + sid + "/assignment/" + aid - - const fetchFile = async () => { - try { - const response = await axios.get(FILE_URL) - setSubmissionFile(response.data.data.files) - } catch (error) { - setSubmissionFile([]) - } - } - - return { submissionFile, fetchFile } -} -const useFetchStudent = (sid : any) => { - const [students, setStudents] = useState([]) - const STUDENT_URL = process.env.REACT_APP_API_URL + '/api/scholarship/user/' + sid - const fetchStudent = async () => { - try { - const response = await axios.get(STUDENT_URL); - setStudents(response.data.data.scholarships); - } catch (err : any) { - console.log(err) - } - } - return {students, fetchStudent} -} export const AssignmentCards = ({ index, scholarship_id, @@ -64,8 +37,39 @@ export const AssignmentCards = ({ onDeleteSuccess, onEditSuccess }: AssignmentCardsProps) => { + // use fetch student + const useFetchStudent = (sid: any) => { + const [students, setStudents] = useState([]) + const STUDENT_URL = process.env.REACT_APP_API_URL + '/api/scholarship/user/' + sid + const fetchStudent = async () => { + try { + const response = await axiosInstance.get(STUDENT_URL); + setStudents(response.data.data.scholarships); + } catch (err: any) { + console.log(err) + } + } + return { students, fetchStudent } + } + //use fetch file + const useFetchFile = (sid: any, aid: any) => { + const [submissionFile, setSubmissionFile] = useState([]) + const FILE_URL = process.env.REACT_APP_API_URL + "/api/files/scholarship/" + sid + "/assignment/" + aid + + const fetchFile = async () => { + try { + const response = await axiosInstance.get(FILE_URL) + setSubmissionFile(response.data.data.files) + } catch (error) { + setSubmissionFile([]) + } + } + + return { submissionFile, fetchFile } + } + const axiosInstance = useAxiosPrivate() const { submissionFile, fetchFile } = useFetchFile(scholarship_id, assignment_id); - const { students, fetchStudent} = useFetchStudent(scholarship_id); + const { students, fetchStudent } = useFetchStudent(scholarship_id); const [applicants, setApplicants] = useState(students.length) const [submissions, setSubmissions] = useState(submissionFile.length) useEffect(() => { @@ -74,7 +78,7 @@ export const AssignmentCards = ({ setSubmissions(submissionFile.length) } fetchData(); - }, [ submissionFile.length]); + }, [submissionFile.length]); useEffect(() => { const fetchData = async () => { await fetchStudent(); @@ -85,7 +89,7 @@ export const AssignmentCards = ({ // TODO: SET THE APPLICANTS AND SUBMSISSIONS @MATTHEW MAHENDRA const [isOpenEditAssignment, setIsOpenEditAssignment] = useState(false) const [isOpenDeleteAssignment, setIsOpenDeleteAssignment] = - React.useState(false) + React.useState(false) const onCloseEditAssignment = () => setIsOpenEditAssignment(false) const onCloseDeleteAssignment = () => setIsOpenDeleteAssignment(false) const onEditAssignment = () => { diff --git a/src/components/Assignment/AssignmentDetails.tsx b/src/components/Assignment/AssignmentDetails.tsx index 9349c7b..5d52ef5 100644 --- a/src/components/Assignment/AssignmentDetails.tsx +++ b/src/components/Assignment/AssignmentDetails.tsx @@ -4,27 +4,30 @@ import { useParams } from "react-router-dom" import axios from "../../api/axios" import { AssignmentCards, AssignmentCardsProps } from "./AssignmentCards" import { CreateAssignmentModal } from "./CreateAssignmentModal" +import useAxiosPrivate from "../../hooks/axiosPrivate" -const useFetchAssignments = () => { - const [assignments, setAssignments] = useState([]) - const { scholarshipid } = useParams() - const fetchAssignments = async () => { - try { - const URL = - process.env.REACT_APP_API_URL + "/api/assignment/" + scholarshipid - const response = await axios.get(URL) - setAssignments(response.data.data) - } catch (error) { - console.error("Error fetching assignments:", error) - setAssignments([]) - } - } +const AssignmentDetails = () => { + const axiosInstance = useAxiosPrivate(); - return { assignments, fetchAssignments } -} + const useFetchAssignments = () => { + const [assignments, setAssignments] = useState([]) + const { scholarshipid } = useParams() -const AssignmentDetails = () => { + const fetchAssignments = async () => { + try { + const URL = + process.env.REACT_APP_API_URL + "/api/assignment/" + scholarshipid + const response = await axiosInstance.get(URL) + setAssignments(response.data.data) + } catch (error) { + console.error("Error fetching assignments:", error) + setAssignments([]) + } + } + + return { assignments, fetchAssignments } + } const { assignments, fetchAssignments } = useFetchAssignments() const [shouldFetchAssignments, setShouldFetchAssignments] = useState(true) diff --git a/src/components/Assignment/CreateAssignmentModal.tsx b/src/components/Assignment/CreateAssignmentModal.tsx index 3932f5c..c95a391 100644 --- a/src/components/Assignment/CreateAssignmentModal.tsx +++ b/src/components/Assignment/CreateAssignmentModal.tsx @@ -22,8 +22,8 @@ import { import React from "react" import { FiEdit } from "react-icons/fi" import { useParams } from "react-router-dom" -import axios from "../../api/axios" import { Field, Form, Formik } from "formik" +import useAxiosPrivate from "../../hooks/axiosPrivate" interface CreateAssignmentModalProps { afterCreate: () => void @@ -32,6 +32,7 @@ interface CreateAssignmentModalProps { export const CreateAssignmentModal = ({ afterCreate }: CreateAssignmentModalProps) => { + const axiosInstance = useAxiosPrivate() const { scholarshipid } = useParams() const { isOpen, onOpen, onClose } = useDisclosure() const [overlay, setOverlay] = React.useState(<ModalOverlay />) @@ -47,7 +48,7 @@ export const CreateAssignmentModal = ({ const assignmentdescription = values.description const URL = process.env.REACT_APP_API_URL + "/api/assignment" - await axios.post(URL, { + await axiosInstance.post(URL, { scholarship_id: Number(scholarshipid), name: assignmentname, desc: assignmentdescription diff --git a/src/components/Assignment/DeleteAssignmentDialog.tsx b/src/components/Assignment/DeleteAssignmentDialog.tsx index bb8bd23..986e2b7 100644 --- a/src/components/Assignment/DeleteAssignmentDialog.tsx +++ b/src/components/Assignment/DeleteAssignmentDialog.tsx @@ -9,7 +9,7 @@ import { Button, useToast } from "@chakra-ui/react" -import axios from "axios" +import useAxiosPrivate from "../../hooks/axiosPrivate" interface DeleteAlertDialogProps { isOpen: boolean @@ -26,6 +26,7 @@ export const DeleteAssignmentDialog: React.FC<DeleteAlertDialogProps> = ({ assignment_id, onDeleteSuccess }) => { + const axiosInstance = useAxiosPrivate(); const toast = useToast() async function DeleteAssignment() { try { @@ -35,7 +36,7 @@ export const DeleteAssignmentDialog: React.FC<DeleteAlertDialogProps> = ({ scholarship_id + "/" + assignment_id - const response = await axios.delete(URL) + const response = await axiosInstance.delete(URL) console.log(response) onClose() onDeleteSuccess() diff --git a/src/components/Assignment/EditAssignmentModal.tsx b/src/components/Assignment/EditAssignmentModal.tsx index 6e2e547..978a25d 100644 --- a/src/components/Assignment/EditAssignmentModal.tsx +++ b/src/components/Assignment/EditAssignmentModal.tsx @@ -20,6 +20,7 @@ import { import axios from "axios" import { Form, Formik, Field } from "formik" import { FiEdit } from "react-icons/fi" +import useAxiosPrivate from "../../hooks/axiosPrivate" interface EditAssignmentModalProps { isOpen: boolean onClose: () => void @@ -35,6 +36,7 @@ export const EditAssignmentModal: React.FC<EditAssignmentModalProps> = ({ assignment_id, onEditSuccess }) => { + const axiosInstance = useAxiosPrivate(); const toast = useToast() const [assignmentName, setAssignmentName] = React.useState("") const [assignmentDescription, setAssignmentDescription] = React.useState("") @@ -47,7 +49,7 @@ export const EditAssignmentModal: React.FC<EditAssignmentModalProps> = ({ scholarship_id + "/" + assignment_id - const response = await axios.get(URL) + const response = await axiosInstance.get(URL) setAssignmentName(response.data.data.assignment_name) setAssignmentDescription(response.data.data.assignment_description) } catch (error) { @@ -63,7 +65,7 @@ export const EditAssignmentModal: React.FC<EditAssignmentModalProps> = ({ scholarship_id + "/" + assignment_id - const response = await axios.patch(URL, { + const response = await axiosInstance.patch(URL, { name: assignmentName, desc: assignmentDescription }) diff --git a/src/components/Assignment/Submission.tsx b/src/components/Assignment/Submission.tsx index 15b2691..5466c8e 100644 --- a/src/components/Assignment/Submission.tsx +++ b/src/components/Assignment/Submission.tsx @@ -2,6 +2,7 @@ import { Box, Text } from "@chakra-ui/react"; import axios from "axios"; import React, { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; +import useAxiosPrivate from "../../hooks/axiosPrivate"; interface FileDetails { file_id: number; @@ -15,48 +16,49 @@ interface UserDetails { email: string; } -const useFetchFile = (sid: any, aid: any) => { - const [submissionFile, setSubmissionFile] = useState<FileDetails[]>([]); - const [userDetailsArray, setUserDetailsArray] = useState<UserDetails[]>([]); +export const Submissions = () => { + const axiosInstance = useAxiosPrivate(); - const FILE_URL = process.env.REACT_APP_API_URL + `/api/files/scholarship/${sid}/assignment/${aid}`; + const useFetchFile = (sid: any, aid: any) => { + const [submissionFile, setSubmissionFile] = useState<FileDetails[]>([]); + const [userDetailsArray, setUserDetailsArray] = useState<UserDetails[]>([]); - const fetchFile = async () => { - try { - const response = await axios.get(FILE_URL); - console.log(response.data.data.files); - setSubmissionFile(response.data.data.files); + const FILE_URL = process.env.REACT_APP_API_URL + `/api/files/scholarship/${sid}/assignment/${aid}`; - const userDetailsPromises = response.data.data.files.map(async (file: FileDetails) => { - try { - const userInfo = await axios.get( - process.env.REACT_APP_API_URL + `/api/user/` + file.user_id_student - ); - return { - userId: file.user_id_student, - name: userInfo.data.data.user.name, - email: userInfo.data.data.user.email, - }; - } catch (error: any) { - console.error( - `Error fetching user details for user_id_student ${file.user_id_student}:`, - error.message - ); - return null; - } - }); + const fetchFile = async () => { + try { + const response = await axiosInstance.get(FILE_URL); + console.log(response.data.data.files); + setSubmissionFile(response.data.data.files); - const userDetailsArray = await Promise.all(userDetailsPromises); - setUserDetailsArray(userDetailsArray.filter((user) => user !== null) as UserDetails[]); - } catch (error) { - setSubmissionFile([]); - } - }; + const userDetailsPromises = response.data.data.files.map(async (file: FileDetails) => { + try { + const userInfo = await axios.get( + process.env.REACT_APP_API_URL + `/api/user/` + file.user_id_student + ); + return { + userId: file.user_id_student, + name: userInfo.data.data.user.name, + email: userInfo.data.data.user.email, + }; + } catch (error: any) { + console.error( + `Error fetching user details for user_id_student ${file.user_id_student}:`, + error.message + ); + return null; + } + }); - return { submissionFile, userDetailsArray, fetchFile }; -}; + const userDetailsArray = await Promise.all(userDetailsPromises); + setUserDetailsArray(userDetailsArray.filter((user) => user !== null) as UserDetails[]); + } catch (error) { + setSubmissionFile([]); + } + }; -export const Submissions = () => { + return { submissionFile, userDetailsArray, fetchFile }; + }; const { scholarshipid, assignmentid } = useParams(); const { submissionFile, userDetailsArray, fetchFile } = useFetchFile( scholarshipid, @@ -69,22 +71,22 @@ export const Submissions = () => { return ( <div> - {submissionFile.map((file, index) => ( - <Box key={index} marginBottom="4" width="100%" borderWidth="1px" borderRadius="lg" overflow="hidden"> - <iframe - title={`Google Drive Viewer ${index}`} - src={file.file_path} - width="100%" - height="700px" - ></iframe> - {userDetailsArray[index] && ( - <Box p="4"> - <Text fontWeight="bold" marginBottom="2">Name: {userDetailsArray[index].name}</Text> - <Text>Email: {userDetailsArray[index].email}</Text> - </Box> - )} + {submissionFile.map((file, index) => ( + <Box key={index} marginBottom="4" width="100%" borderWidth="1px" borderRadius="lg" overflow="hidden"> + <iframe + title={`Google Drive Viewer ${index}`} + src={file.file_path} + width="100%" + height="700px" + ></iframe> + {userDetailsArray[index] && ( + <Box p="4"> + <Text fontWeight="bold" marginBottom="2">Name: {userDetailsArray[index].name}</Text> + <Text>Email: {userDetailsArray[index].email}</Text> </Box> - ))} + )} + </Box> + ))} </div> ); }; diff --git a/src/components/Dashboard/UniversityDashboard.tsx b/src/components/Dashboard/UniversityDashboard.tsx index 863341c..44b1635 100644 --- a/src/components/Dashboard/UniversityDashboard.tsx +++ b/src/components/Dashboard/UniversityDashboard.tsx @@ -10,11 +10,12 @@ import { Button } from "@chakra-ui/react" import { Link } from "react-router-dom" -import axios from "axios" import { useEffect, useState } from "react" +import useAxiosPrivate from "../../hooks/axiosPrivate" import { handleGetInfo } from "../../utils/auth" export const UniversityDashboard = () => { + const axiosInstance = useAxiosPrivate() const [userInfo, setUserInfo] = useState({ user_id: 0, name: "", @@ -35,11 +36,11 @@ export const UniversityDashboard = () => { role: response?.data.roles }); - const students = await axios.get( + const students = await axiosInstance.get( process.env.REACT_APP_API_URL + "/api/university/stats/" + response?.data.user_id ); - setStudentCount(students.data.data.data[0].applicant_count) + setStudentCount(students.data.data.data[0].applicant_count) } catch (error) { console.error("Error in useEffect:", error); } diff --git a/src/components/Home/Home.tsx b/src/components/Home/Home.tsx index f770f96..0e91ab4 100644 --- a/src/components/Home/Home.tsx +++ b/src/components/Home/Home.tsx @@ -34,7 +34,7 @@ const Home = () => { justifyContent="center" > <Link - to = {`/scholarships`} + to={`/scholarships`} > <ArrowForwardIcon w={6} h={6} mr={2} /> View Scholarships </Link> @@ -71,7 +71,7 @@ const Home = () => { justifyContent="center" > <Link - to = {`/dashboard`} + to={`/dashboard`} > <ArrowForwardIcon w={6} h={6} mr={2} /> View Dashboard </Link> diff --git a/src/components/Home/UniversityHome.tsx b/src/components/Home/UniversityHome.tsx index 42037f3..c942a84 100644 --- a/src/components/Home/UniversityHome.tsx +++ b/src/components/Home/UniversityHome.tsx @@ -34,9 +34,9 @@ const UniversityHome = () => { justifyContent="center" > <Link - to = {`/report`} + to={`/report`} > - <ArrowForwardIcon w={6} h={6} mr={2} /> View Students + <ArrowForwardIcon w={6} h={6} mr={2} /> View Students </Link> </Box> <Box @@ -49,7 +49,7 @@ const UniversityHome = () => { justifyContent="center" > <Link - to = {`/dashboard`} + to={`/dashboard`} > <ArrowForwardIcon w={6} h={6} mr={2} /> View Dashboard </Link> diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index 72c5443..353f242 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -20,10 +20,10 @@ import { } from "@chakra-ui/react"; import { Link, useNavigate } from "react-router-dom"; import { FaSun, FaMoon } from "react-icons/fa"; -import { handleLogin } from "../../utils/auth"; import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; - +import { handleLogin } from "../../utils/auth"; const Login = () => { + // const {handleLogin} = useAuth() const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [errMsg, setErrMsg] = useState(""); @@ -58,7 +58,7 @@ const Login = () => { {/* IMAGE */} <Box display={{ base: "none", md: "block" }} - w={{ base: "0%", md: "50%" }} + w={{ base: "0%", md: "50%" }} overflow="hidden" > <Image @@ -104,22 +104,22 @@ const Login = () => { </FormControl> <FormControl variant="floating" id="password" isRequired> <InputGroup> - <Input - placeholder=" " - type={showPassword ? "text" : "password"} - mb={5} - required - value={password} - onChange={(e) => setPassword(e.target.value)} - /> - <FormLabel bg={formBackground}>Password</FormLabel> + <Input + placeholder=" " + type={showPassword ? "text" : "password"} + mb={5} + required + value={password} + onChange={(e) => setPassword(e.target.value)} + /> + <FormLabel bg={formBackground}>Password</FormLabel> <InputRightElement> <IconButton aria-label={showPassword ? "Hide password" : "Show password"} icon={showPassword ? <ViewOffIcon /> : <ViewIcon />} onClick={handlePasswordVisibility} variant="ghost" - /> + /> </InputRightElement> </InputGroup> </FormControl> diff --git a/src/components/Register/RegisterOrg.tsx b/src/components/Register/RegisterOrg.tsx index 50e304e..6686b91 100644 --- a/src/components/Register/RegisterOrg.tsx +++ b/src/components/Register/RegisterOrg.tsx @@ -23,12 +23,14 @@ import { Link } from "react-router-dom" import axios from "../../api/axios" import { redirect } from "react-router-dom" import { FaSun, FaMoon } from "react-icons/fa"; +import useAxiosPrivate from "../../hooks/axiosPrivate"; const EMAIL_REGEX = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/ const PWD_REGEX = /^(?=.*\d).{8,}$/ const REGISTER_URL = "/api/organization" // const navigate = useNavigate(); const RegisterOrg = () => { + const axiosInstance = useAxiosPrivate(); const [showPassword, setShowPassword] = useState(false) const { colorMode, toggleColorMode } = useColorMode() const [showSuccessMessage, setShowSuccessMessage] = useState(false); @@ -88,19 +90,19 @@ const RegisterOrg = () => { setErrMsg("Password did not match") return } - try { - const response = await axios.post( + try { + const response = await axiosInstance.post( REGISTER_URL, { - name : name, - email : email, - password : password, - address : address, - organizationDescription : organizationDescription, - referral_code : referralCode + name: name, + email: email, + password: password, + address: address, + organizationDescription: organizationDescription, + referral_code: referralCode }, { - headers: {"X-API-KEY" : "kunciT", "Content-Type" : "application/json"} + headers: { "X-API-KEY": "kunciT", "Content-Type": "application/json" } } ) setShowSuccessMessage(true); @@ -126,17 +128,17 @@ const RegisterOrg = () => { } return ( <motion.div initial={{ opacity: 0, y: -50 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}> - <Flex h="100vh"> - {/* FORM */} - <Flex - flexDirection="column" - w={{ base: "100%", md: "50%" }} - maxW="1000px" - rounded={6} - bg={formBackground} - p={12} - > - <Heading mb={6}>Register Organization</Heading> + <Flex h="100vh"> + {/* FORM */} + <Flex + flexDirection="column" + w={{ base: "100%", md: "50%" }} + maxW="1000px" + rounded={6} + bg={formBackground} + p={12} + > + <Heading mb={6}>Register Organization</Heading> <Box color="red.500" display={errMsg ? "block" : "none"} @@ -154,13 +156,13 @@ const RegisterOrg = () => { )} <FormControl variant="floating" id="name" isRequired mb={6}> <Input - placeholder=" " - type="text" - required - value={name} - onChange={(e) => setName(e.target.value)} - /> - <FormLabel bg={formBackground}>Name </FormLabel> + placeholder=" " + type="text" + required + value={name} + onChange={(e) => setName(e.target.value)} + /> + <FormLabel bg={formBackground}>Name </FormLabel> </FormControl> <FormControl variant="floating" id="email" isRequired isInvalid={!validEmail} mb={6}> <Input @@ -185,7 +187,7 @@ const RegisterOrg = () => { onChange={(e) => setPassword(e.target.value)} onBlur={() => setValidPwd(PWD_REGEX.test(password))} required - /> + /> <FormLabel bg={formBackground}>Password</FormLabel> <InputRightElement> <IconButton @@ -193,7 +195,7 @@ const RegisterOrg = () => { icon={showPassword ? <ViewOffIcon /> : <ViewIcon />} onClick={handlePasswordVisibility} variant="ghost" - /> + /> </InputRightElement> </InputGroup> <FormErrorMessage> @@ -209,7 +211,7 @@ const RegisterOrg = () => { onChange={(e) => setMatchPwd(e.target.value)} onBlur={() => setValidMatch(password === matchPwd)} required - /> + /> <FormLabel bg={formBackground}>Password Confirmation</FormLabel> <InputRightElement> <IconButton @@ -217,7 +219,7 @@ const RegisterOrg = () => { icon={showPassword ? <ViewOffIcon /> : <ViewIcon />} onClick={handlePasswordVisibility} variant="ghost" - /> + /> </InputRightElement> </InputGroup> <FormErrorMessage> @@ -231,7 +233,7 @@ const RegisterOrg = () => { required value={address} onChange={(e) => setAddress(e.target.value)} - /> + /> <FormLabel bg={formBackground}>Address </FormLabel> </FormControl> <FormControl variant="floating" id="organizationDescription" isRequired mb={6}> @@ -241,7 +243,7 @@ const RegisterOrg = () => { required value={organizationDescription} onChange={(e) => setOrganizationDescription(e.target.value)} - /> + /> <FormLabel bg={formBackground}>Description </FormLabel> </FormControl> <FormControl variant="floating" id="organizationDescription" isRequired mb={6}> @@ -251,19 +253,19 @@ const RegisterOrg = () => { required value={referralCode} onChange={(e) => setReferralCode(e.target.value)} - /> + /> <FormLabel bg={formBackground}>Refferal Code </FormLabel> </FormControl> - <Button bg={buttonColor} color={formBackground}_hover={{ bg: "gray.600", color: "gray.200" }} mb={8} onClick={handleSubmit}> + <Button bg={buttonColor} color={formBackground} _hover={{ bg: "gray.600", color: "gray.200" }} mb={8} onClick={handleSubmit}> Register </Button> <Box position="absolute" top="2" left="2"> - <IconButton - aria-label="Toggle Dark Mode" - icon={colorMode === "dark" ? <FaSun /> : <FaMoon />} - color={buttonColor} - onClick={toggleColorMode} - /> + <IconButton + aria-label="Toggle Dark Mode" + icon={colorMode === "dark" ? <FaSun /> : <FaMoon />} + color={buttonColor} + onClick={toggleColorMode} + /> </Box> <FormControl> <FormLabel> @@ -273,18 +275,18 @@ const RegisterOrg = () => { </Flex> {/* IMAGE */} <Box - display={{ base: "none", md: "block" }} - alignItems="center" - w={{ base: "0%", md: "50%" }} - overflow="hidden" - > - <Image - src="https://imageio.forbes.com/specials-images/imageserve/64e6668d9ed8aec53b4af6bf/The-University-of-California--Los-Angeles-campus-/0x0.jpg?format=jpg&height=1080&width=1080" - h="100%" - alt="Register" - /> - </Box> - </Flex> + display={{ base: "none", md: "block" }} + alignItems="center" + w={{ base: "0%", md: "50%" }} + overflow="hidden" + > + <Image + src="https://imageio.forbes.com/specials-images/imageserve/64e6668d9ed8aec53b4af6bf/The-University-of-California--Los-Angeles-campus-/0x0.jpg?format=jpg&height=1080&width=1080" + h="100%" + alt="Register" + /> + </Box> + </Flex> </motion.div> ) } diff --git a/src/components/Register/RegisterUniversity.tsx b/src/components/Register/RegisterUniversity.tsx index 8010b21..b352d10 100644 --- a/src/components/Register/RegisterUniversity.tsx +++ b/src/components/Register/RegisterUniversity.tsx @@ -23,12 +23,14 @@ import { Link } from "react-router-dom" import axios from "../../api/axios" import { redirect } from "react-router-dom" import { FaSun, FaMoon } from "react-icons/fa"; +import useAxiosPrivate from "../../hooks/axiosPrivate"; const EMAIL_REGEX = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/ const PWD_REGEX = /^(?=.*\d).{8,}$/ const REGISTER_URL = "/api/university" // const navigate = useNavigate(); const RegisterUni = () => { + const axiosInstance = useAxiosPrivate() const [showPassword, setShowPassword] = useState(false) const { colorMode, toggleColorMode } = useColorMode() const [showSuccessMessage, setShowSuccessMessage] = useState(false); @@ -84,18 +86,18 @@ const RegisterUni = () => { setErrMsg("Password did not match") return } - try { - const response = await axios.post( + try { + const response = await axiosInstance.post( REGISTER_URL, { - name : name, - email : email, - password : password, - address : address, - universityDescription : universityDescription, + name: name, + email: email, + password: password, + address: address, + universityDescription: universityDescription, }, { - headers: {"X-API-KEY" : "kunciT", "Content-Type" : "application/json"} + headers: { "X-API-KEY": "kunciT", "Content-Type": "application/json" } } ) setShowSuccessMessage(true); @@ -120,17 +122,17 @@ const RegisterUni = () => { } return ( <motion.div initial={{ opacity: 0, y: -50 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}> - <Flex h="100vh"> - {/* FORM */} - <Flex - flexDirection="column" - w={{ base: "100%", md: "50%" }} - maxW="1000px" - rounded={6} - bg={formBackground} - p={12} - > - <Heading mb={6}>Register University</Heading> + <Flex h="100vh"> + {/* FORM */} + <Flex + flexDirection="column" + w={{ base: "100%", md: "50%" }} + maxW="1000px" + rounded={6} + bg={formBackground} + p={12} + > + <Heading mb={6}>Register University</Heading> <Box color="red.500" display={errMsg ? "block" : "none"} @@ -148,13 +150,13 @@ const RegisterUni = () => { )} <FormControl variant="floating" id="name" isRequired mb={6}> <Input - placeholder=" " - type="text" - required - value={name} - onChange={(e) => setName(e.target.value)} - /> - <FormLabel bg={formBackground}>Name </FormLabel> + placeholder=" " + type="text" + required + value={name} + onChange={(e) => setName(e.target.value)} + /> + <FormLabel bg={formBackground}>Name </FormLabel> </FormControl> <FormControl variant="floating" id="email" isRequired isInvalid={!validEmail} mb={6}> <Input @@ -179,7 +181,7 @@ const RegisterUni = () => { onChange={(e) => setPassword(e.target.value)} onBlur={() => setValidPwd(PWD_REGEX.test(password))} required - /> + /> <FormLabel bg={formBackground}>Password</FormLabel> <InputRightElement> <IconButton @@ -187,7 +189,7 @@ const RegisterUni = () => { icon={showPassword ? <ViewOffIcon /> : <ViewIcon />} onClick={handlePasswordVisibility} variant="ghost" - /> + /> </InputRightElement> </InputGroup> <FormErrorMessage> @@ -203,7 +205,7 @@ const RegisterUni = () => { onChange={(e) => setMatchPwd(e.target.value)} onBlur={() => setValidMatch(password === matchPwd)} required - /> + /> <FormLabel bg={formBackground}>Password Confirmation</FormLabel> <InputRightElement> <IconButton @@ -211,7 +213,7 @@ const RegisterUni = () => { icon={showPassword ? <ViewOffIcon /> : <ViewIcon />} onClick={handlePasswordVisibility} variant="ghost" - /> + /> </InputRightElement> </InputGroup> <FormErrorMessage> @@ -225,7 +227,7 @@ const RegisterUni = () => { required value={address} onChange={(e) => setAddress(e.target.value)} - /> + /> <FormLabel bg={formBackground}>Address </FormLabel> </FormControl> <FormControl variant="floating" id="universityDescription" isRequired mb={6}> @@ -235,19 +237,19 @@ const RegisterUni = () => { required value={universityDescription} onChange={(e) => setuniversityDescription(e.target.value)} - /> + /> <FormLabel bg={formBackground}>Description </FormLabel> </FormControl> - <Button bg={buttonColor} color={formBackground}_hover={{ bg: "gray.600", color: "gray.200" }} mb={8} onClick={handleSubmit}> + <Button bg={buttonColor} color={formBackground} _hover={{ bg: "gray.600", color: "gray.200" }} mb={8} onClick={handleSubmit}> Register </Button> <Box position="absolute" top="2" left="2"> - <IconButton - aria-label="Toggle Dark Mode" - icon={colorMode === "dark" ? <FaSun /> : <FaMoon />} - color={buttonColor} - onClick={toggleColorMode} - /> + <IconButton + aria-label="Toggle Dark Mode" + icon={colorMode === "dark" ? <FaSun /> : <FaMoon />} + color={buttonColor} + onClick={toggleColorMode} + /> </Box> <FormControl> <FormLabel> @@ -257,18 +259,18 @@ const RegisterUni = () => { </Flex> {/* IMAGE */} <Box - display={{ base: "none", md: "block" }} - alignItems="center" - w={{ base: "0%", md: "50%" }} - overflow="hidden" - > - <Image - src="https://imageio.forbes.com/specials-images/imageserve/64e6668d9ed8aec53b4af6bf/The-University-of-California--Los-Angeles-campus-/0x0.jpg?format=jpg&height=1080&width=1080" - h="100%" - alt="Register" - /> - </Box> - </Flex> + display={{ base: "none", md: "block" }} + alignItems="center" + w={{ base: "0%", md: "50%" }} + overflow="hidden" + > + <Image + src="https://imageio.forbes.com/specials-images/imageserve/64e6668d9ed8aec53b4af6bf/The-University-of-California--Los-Angeles-campus-/0x0.jpg?format=jpg&height=1080&width=1080" + h="100%" + alt="Register" + /> + </Box> + </Flex> </motion.div> ) } diff --git a/src/components/Report/Report.tsx b/src/components/Report/Report.tsx index 5d36c89..7188b4c 100644 --- a/src/components/Report/Report.tsx +++ b/src/components/Report/Report.tsx @@ -24,7 +24,6 @@ import { } from "@chakra-ui/react" import axios from "axios" import React, { useState, useEffect } from "react" -import { handleGetInfo } from "../../utils/auth" import { ArrowBackIcon, ArrowForwardIcon, @@ -33,6 +32,8 @@ import { } from "@chakra-ui/icons" import { FaFilter } from "react-icons/fa" import { debounce, get } from "lodash" +import useAxiosPrivate from "../../hooks/axiosPrivate" +import { handleGetInfo } from "../../utils/auth" const Report: React.FC = () => { const [userInfo, setUserInfo] = useState({ @@ -41,7 +42,8 @@ const Report: React.FC = () => { email: "", role: "" }); - + const axiosInstance = useAxiosPrivate() + const [userId, setUserId] = useState(0); const [students, setStudents] = useState([]); const [currentPage, setCurrentPage] = useState(1); @@ -49,7 +51,7 @@ const Report: React.FC = () => { const [search, setSearch] = useState(""); const [numberOfPages, setNumberOfPages] = useState(0); const MAX_PAGE_BUTTONS = 3; - + const fetchStudents = async () => { const url = new URL( process.env.REACT_APP_API_URL + "/api/university/stats/" + userId @@ -60,14 +62,14 @@ const Report: React.FC = () => { params.append("currentPage", String(currentPage)); console.log(params.toString()); url.search = params.toString(); - const response = await axios.get(url.toString()); + const response = await axiosInstance.get(url.toString()); const students = await response.data; setNumberOfPages(Math.ceil(students.data.total / itemsPerPage || 100)); setStudents(students.data.data); }; - + const debounceFetch = debounce(fetchStudents, 500); - + useEffect(() => { const getInfoAndFetchStudents = async () => { const response = await handleGetInfo(); @@ -77,22 +79,22 @@ const Report: React.FC = () => { email: response?.data.email, role: response?.data.roles }); - + if (userId === 0 && response?.data.user_id !== 0) { setUserId(response?.data.user_id); } debounceFetch(); }; - + getInfoAndFetchStudents(); }, [search, itemsPerPage, currentPage, userId]); useEffect(() => { setCurrentPage(1); }, [itemsPerPage]) - - const startPage = + + const startPage = currentPage <= Math.floor(MAX_PAGE_BUTTONS / 2) ? 1 : Math.max(1, currentPage - Math.floor(MAX_PAGE_BUTTONS / 2)) diff --git a/src/components/Scholarships/Scholarships.tsx b/src/components/Scholarships/Scholarships.tsx index 971f14b..3f352d1 100644 --- a/src/components/Scholarships/Scholarships.tsx +++ b/src/components/Scholarships/Scholarships.tsx @@ -34,6 +34,7 @@ import { CheckIcon, ChevronDownIcon, Search2Icon } from "@chakra-ui/icons" import { debounce } from "lodash" import { DataTable } from "./DataTable" import axios from "axios" +import useAxiosPrivate from "../../hooks/axiosPrivate" const Scholarships: React.FC = () => { type Scholarship = { @@ -44,6 +45,7 @@ const Scholarships: React.FC = () => { coverage: Number action: JSX.Element } + const axiosInstance = useAxiosPrivate() const columnHelper = createColumnHelper<(typeof scholarships)[0]>() @@ -88,7 +90,7 @@ const Scholarships: React.FC = () => { params.append("currentPage", currentPage.toString()) api_url.search = params.toString() - const response = await axios.get(api_url.toString()) + const response = await axiosInstance.get(api_url.toString()) if (!response) { throw new Error("Error fetching scholarships.") @@ -135,14 +137,14 @@ const Scholarships: React.FC = () => { </Link> </Tooltip> <Tooltip label={`${scholarship.count} views`}> - <Button - variant="ghost" - colorScheme="grey" - size="sm" - leftIcon={<Icon as={ViewIcon} />} - > - {`${scholarship.count} views`} - </Button> + <Button + variant="ghost" + colorScheme="grey" + size="sm" + leftIcon={<Icon as={ViewIcon} />} + > + {`${scholarship.count} views`} + </Button> </Tooltip> </Stack> ) @@ -155,7 +157,7 @@ const Scholarships: React.FC = () => { const fetchScholarshipTypes = async () => { try { - const response = await axios.get( + const response = await axiosInstance.get( process.env.REACT_APP_API_URL + "/api/scholarshiptype" ) diff --git a/src/components/Sidebar/MobileNav.tsx b/src/components/Sidebar/MobileNav.tsx index d9e6e03..2ebdcb2 100644 --- a/src/components/Sidebar/MobileNav.tsx +++ b/src/components/Sidebar/MobileNav.tsx @@ -17,10 +17,8 @@ import { FiMenu, FiChevronDown } from "react-icons/fi" import { Link, useNavigate } from "react-router-dom" import { ReactComponent as Logo } from "../../assets/logo-1.svg" import AvatarWithRipple from "../Avatar/Avatar" -import { handleLogout } from "../../utils/auth" import { useState, useEffect } from "react" -import { handleGetInfo } from "../../utils/auth" - +import { handleGetInfo, handleLogout } from "../../utils/auth" interface MobileProps extends FlexProps { onOpen: () => void } diff --git a/src/components/Sidebar/SidebarContent.tsx b/src/components/Sidebar/SidebarContent.tsx index 0ff60f4..2e960eb 100644 --- a/src/components/Sidebar/SidebarContent.tsx +++ b/src/components/Sidebar/SidebarContent.tsx @@ -10,9 +10,8 @@ import { FiHome, FiBarChart, FiCompass, FiBriefcase } from "react-icons/fi" import { IconType } from "react-icons" import NavItem from "./NavItem" import { ReactComponent as Logo } from "../../assets/logo-1.svg" -import { handleGetInfo } from "../../utils/auth" import { useEffect, useState } from "react" - +import { handleGetInfo } from "../../utils/auth" interface SidebarProps extends BoxProps { onClose: () => void } @@ -29,7 +28,6 @@ const SidebarContent = ({ onClose, ...rest }: SidebarProps) => { email: "", role: "" }) - const getInfo = async () => { const response = await handleGetInfo() const name = response?.data.name diff --git a/src/hooks/axiosPrivate.tsx b/src/hooks/axiosPrivate.tsx index 7b0b0d7..4509255 100644 --- a/src/hooks/axiosPrivate.tsx +++ b/src/hooks/axiosPrivate.tsx @@ -1,47 +1,34 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { axiosPrivate } from "../api/axios" -import { useEffect } from "react" -import useRefreshToken from "./useRefreshToken" -import Cookies from "js-cookie" +import { axiosPrivate } from "../api/axios"; +import { useEffect } from "react"; +import useRefreshToken from "./useRefreshToken"; +import Cookies from "js-cookie"; const useAxiosPrivate = () => { - const refresh = useRefreshToken() - const accToken = Cookies.get("accToken") + const refresh = useRefreshToken(); useEffect(() => { const requestIntercept = axiosPrivate.interceptors.request.use( - (config) => { - if (!config.headers["Authorization"]) { - config.headers["Authorization"] = `Bearer ${accToken}` + async (config) => { + try { + // Ensure that the access token is up-to-date before making the request + await refresh(); + + // Set the new access token in the request headers + // config.headers["Authorization"] = `Bearer ${newAccessToken}`; + return config; + } catch (error) { + return Promise.reject(error); } - return config }, (error) => Promise.reject(error) - ) - - const responseIntercept = axiosPrivate.interceptors.response.use( - (response) => response, - async (error) => { - const prevRequest = error?.config - if (error?.response?.status === 403 && !prevRequest?.sent) { - prevRequest.sent = true - const refResponse = await refresh() - Cookies.set("accToken", refResponse.data.accessToken) - prevRequest.headers[ - "Authorization" - ] = `Bearer ${refResponse.data.accessToken}` - return axiosPrivate(prevRequest) - } - return Promise.reject(error) - } - ) + ); return () => { - axiosPrivate.interceptors.request.eject(requestIntercept) - axiosPrivate.interceptors.response.eject(responseIntercept) - } - }, [refresh]) + axiosPrivate.interceptors.request.eject(requestIntercept); + }; + }, [refresh]); - return axiosPrivate -} + return axiosPrivate; +}; -export default useAxiosPrivate +export default useAxiosPrivate; diff --git a/src/hooks/useRefreshToken.tsx b/src/hooks/useRefreshToken.tsx index 3dc5234..57464b1 100644 --- a/src/hooks/useRefreshToken.tsx +++ b/src/hooks/useRefreshToken.tsx @@ -1,4 +1,5 @@ import axios from "../api/axios" +import Cookies from "js-cookie" const useRefreshToken = () => { const refresh = async () => { diff --git a/src/utils/RequireAuth.tsx b/src/utils/RequireAuth.tsx index ee21f21..eb6ca37 100644 --- a/src/utils/RequireAuth.tsx +++ b/src/utils/RequireAuth.tsx @@ -1,8 +1,7 @@ import { useLocation, Outlet, useNavigate } from "react-router-dom" import { useState, useEffect } from "react" -import { handleGetInfo } from "../utils/auth" import useRefreshToken from "../hooks/useRefreshToken" -import { isatty } from "tty" +import { handleGetInfo } from "./auth" const RequireAuth = ({ allowedRoles }: any) => { const location = useLocation() @@ -10,43 +9,24 @@ const RequireAuth = ({ allowedRoles }: any) => { const navigate = useNavigate() const refresh = useRefreshToken() - const fetchData = async () => { - try { - const response = await handleGetInfo() - const roles = response?.data.roles - setUserRoles(roles) - if (roles) { - const isAuthorized = allowedRoles.includes(roles) - if (!isAuthorized) { - navigate("/unauthorized", { state: { from: location } }) - } - } else { - try { - await refresh() - const refreshedResponse = await handleGetInfo() - const roles = refreshedResponse?.data.roles - setUserRoles(roles) - const isAuthorized = allowedRoles.includes(roles) - if (!isAuthorized) { - navigate("/unauthorized", { state: { from: location } }) - } - } catch (refreshError) { - console.error("Error refreshing token:", refreshError) - navigate("/login", { state: { from: location } }) - } + const fetchData = async () => { + try { + const response = await handleGetInfo() + const roles = response?.data.roles + setUserRoles(roles) + if (roles) { + const isAuthorized = allowedRoles.includes(roles) + if (!isAuthorized) { + navigate("/unauthorized", { state: { from: location } }) } - } catch (error) { - console.error("Error fetching roles:", error) + } else { try { await refresh() const refreshedResponse = await handleGetInfo() const roles = refreshedResponse?.data.roles setUserRoles(roles) - const isAuthorized = allowedRoles.includes(roles) - if (!isAuthorized) { - console.log("Unauthorized after refresh") navigate("/unauthorized", { state: { from: location } }) } } catch (refreshError) { @@ -54,9 +34,27 @@ const RequireAuth = ({ allowedRoles }: any) => { navigate("/login", { state: { from: location } }) } } + } catch (error) { + console.error("Error fetching roles:", error) + try { + await refresh() + const refreshedResponse = await handleGetInfo() + const roles = refreshedResponse?.data.roles + setUserRoles(roles) + + const isAuthorized = allowedRoles.includes(roles) + + if (!isAuthorized) { + navigate("/unauthorized", { state: { from: location } }) + } + } catch (refreshError) { + console.error("Error refreshing token:", refreshError) + navigate("/login", { state: { from: location } }) + } } + } - fetchData() + fetchData() return <Outlet /> } diff --git a/src/utils/auth.tsx b/src/utils/auth.tsx index 00f03dc..c80ee97 100644 --- a/src/utils/auth.tsx +++ b/src/utils/auth.tsx @@ -57,5 +57,5 @@ async function handleGetInfo() { throw error } } - export { handleLogin, handleLogout, handleGetInfo } + -- GitLab