diff --git a/package-lock.json b/package-lock.json
index 2bcc9b8..134d859 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,8 @@
"@testing-library/user-event": "^13.5.0",
"axios": "^1.4.0",
"bootstrap": "^5.2.3",
+ "dayjs": "^1.11.9",
+ "jwt-decode": "^3.1.2",
"react": "^18.2.0",
"react-bootstrap": "^2.7.4",
"react-dom": "^18.2.0",
@@ -6590,6 +6592,11 @@
"node": ">=10"
}
},
+ "node_modules/dayjs": {
+ "version": "1.11.9",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz",
+ "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA=="
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -11837,6 +11844,11 @@
"node": ">=4.0"
}
},
+ "node_modules/jwt-decode": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
+ "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
+ },
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
diff --git a/package.json b/package.json
index c8cb977..f47938a 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,8 @@
"@testing-library/user-event": "^13.5.0",
"axios": "^1.4.0",
"bootstrap": "^5.2.3",
+ "dayjs": "^1.11.9",
+ "jwt-decode": "^3.1.2",
"react": "^18.2.0",
"react-bootstrap": "^2.7.4",
"react-dom": "^18.2.0",
diff --git a/src/App.js b/src/App.js
index 667ccf2..594831e 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,12 +1,14 @@
import logo from "./logo.svg";
import "./App.css";
-import { Route, Routes } from "react-router-dom";
+import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Image from "react-bootstrap/Image";
import IddrsImg from "./Static/Imgs/IDDRS.png";
import Container from "react-bootstrap/esm/Container";
import AppContext from "./AppContext";
-
+import PrivateRoute from "./Utiles/PrivateRoute";
import Navbar_ from "./Components/Navbar/Navbar_";
+import { AuthProvider } from "./Context/AuthContext";
+import { URLProvider } from "./Context/URLContext";
import Home from "./Pages/Home";
import Levels from "./Pages/Levels";
@@ -14,26 +16,48 @@ import Content from "./Pages/Content";
import NewParagraph from "./Pages/NewParagraph";
import Standards from "./Pages/Standards";
import Details from "./Pages/Details";
+import Login from "./Pages/Login";
function App() {
- const iddrs_url = "http://localhost:8000/admin_api";
-
return (
-
-
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+
+
+
+ }>
+ } />
+
+ }>
+ } />
+
+ }>
+ } />
+
+ }>
+ } />
+
+ }>
+ } />
+
+ }
+ >
+ }
+ />
+
+
+ } />
+
+
+
+
);
}
diff --git a/src/Components/Content/ContentComp.js b/src/Components/Content/ContentComp.js
index e312300..1992bc4 100644
--- a/src/Components/Content/ContentComp.js
+++ b/src/Components/Content/ContentComp.js
@@ -1,12 +1,13 @@
import React, { useContext, useState, useEffect } from "react";
-import AppContext from "../../AppContext";
+import URLContext from "../../Context/URLContext";
import Form from "react-bootstrap/Form";
import Container from "react-bootstrap/Container";
import axios from "axios";
import ContentList from "./ContentList";
+import useAxios from "../../Utiles/useAxios";
const ContentComp = () => {
- const url = useContext(AppContext);
+ let { url } = useContext(URLContext);
const [levels, setLevels] = useState([]);
const [standards, setStandards] = useState([]);
@@ -19,12 +20,19 @@ const ContentComp = () => {
const [data, setData] = useState([]);
- useEffect(() => {
- axios
- .get(url + "/levels/")
- .then((response) => setLevels(response.data))
- .catch((error) => console.log(error));
- }, []);
+ let api = useAxios();
+
+ let getLevels = async () => {
+ let response = await api.get("/levels/");
+ if (response.status === 200) {
+ setLevels(response.data);
+ }
+ };
+
+ useEffect(()=> {
+ getLevels()
+}, [])
+
useEffect(() => {
axios
@@ -60,7 +68,6 @@ const ContentComp = () => {
return (
-
{
))}
)}
-
+
);
};
diff --git a/src/Components/HomePage/HomePage.js b/src/Components/HomePage/HomePage.js
index 6c9183f..e7bc8d4 100644
--- a/src/Components/HomePage/HomePage.js
+++ b/src/Components/HomePage/HomePage.js
@@ -1,5 +1,5 @@
import React, { useContext, useState, useEffect } from "react";
-import AppContext from "../../AppContext";
+import URLContext from "../../Context/URLContext";
import Button from "react-bootstrap/esm/Button";
import axios from "axios";
import Row from "react-bootstrap/Row";
@@ -8,7 +8,7 @@ import Classes from "./HomePage.module.css";
import ClipLoader from "react-spinners/ClipLoader";
const HomePage = () => {
- const url = useContext(AppContext);
+ let {url} = useContext(URLContext)
const [newContentTracker, setNewContentTracker] = useState([]);
const [newContents, setNewContents] = useState(true);
const [isLoading, setIsLoading] = useState(false);
diff --git a/src/Components/LevelStandard/LevelStandard.js b/src/Components/LevelStandard/LevelStandard.js
index 2933947..49da71a 100644
--- a/src/Components/LevelStandard/LevelStandard.js
+++ b/src/Components/LevelStandard/LevelStandard.js
@@ -1,10 +1,10 @@
import React, { useContext, useState, useEffect } from "react";
-import AppContext from "../../AppContext";
+import URLContext from "../../Context/URLContext";
import axios from "axios";
import Form from "react-bootstrap/Form";
const LevelStandard = ({ onValueChange }) => {
- const url = useContext(AppContext);
+ let {url} = useContext(URLContext)
const [levels, setLevels] = useState([]);
const [standards, setStandards] = useState([]);
diff --git a/src/Components/LevelsList/LevelsList.js b/src/Components/LevelsList/LevelsList.js
index 9345d5d..00d87e4 100644
--- a/src/Components/LevelsList/LevelsList.js
+++ b/src/Components/LevelsList/LevelsList.js
@@ -1,5 +1,5 @@
import React, { useContext, useState, useEffect } from "react";
-import AppContext from "../../AppContext";
+import URLContext from "../../Context/URLContext";
import axios from "axios";
import Button from "react-bootstrap/Button";
import Table from "react-bootstrap/Table";
@@ -8,7 +8,7 @@ import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
const LevelsList = () => {
- const url = useContext(AppContext);
+ let { url } = useContext(URLContext);
const [editingRow, setEditingRow] = useState(null);
const [levels, setLevels] = useState([]);
@@ -32,7 +32,6 @@ const LevelsList = () => {
})
.then((response) => {
console.log("Form submitted successfully:", response.data);
-
})
.catch((error) => {
console.error("Error submitting form:", error);
diff --git a/src/Components/LoginPage/LoginPage.js b/src/Components/LoginPage/LoginPage.js
new file mode 100644
index 0000000..8f0edd5
--- /dev/null
+++ b/src/Components/LoginPage/LoginPage.js
@@ -0,0 +1,35 @@
+import React, { useContext } from "react";
+import Form from "react-bootstrap/Form";
+import Button from "react-bootstrap/Button";
+import AuthContext from "../../Context/AuthContext";
+
+const LoginPage = () => {
+ let { loginUser } = useContext(AuthContext);
+ return (
+
+
+ Email address
+
+
+
+ Password
+
+
+
+
+
+ );
+};
+
+export default LoginPage;
diff --git a/src/Components/Navbar/Navbar_.js b/src/Components/Navbar/Navbar_.js
index 4a7553c..d9e4d5d 100644
--- a/src/Components/Navbar/Navbar_.js
+++ b/src/Components/Navbar/Navbar_.js
@@ -1,11 +1,14 @@
-import React from "react";
+import React, {useContext} from "react";
import Nav from "react-bootstrap/Nav";
import Navbar from "react-bootstrap/Navbar";
import Container from "react-bootstrap/esm/Container";
import { NavLink } from "react-router-dom";
import classes from "./Navbar_.module.css";
+import AuthContext from "../../Context/AuthContext";
+import Button from "react-bootstrap/esm/Button";
const Navbar_ = () => {
+ let {user, logoutUser} = useContext(AuthContext);
return (
@@ -52,6 +55,13 @@ const Navbar_ = () => {
>
Standards
+
+ {user && Hello { user.username }
}
+
+ {user && (
+
+ )}
+
diff --git a/src/Components/NewParagraphForm/NewParagraphForm.js b/src/Components/NewParagraphForm/NewParagraphForm.js
index 751a577..4c079a0 100644
--- a/src/Components/NewParagraphForm/NewParagraphForm.js
+++ b/src/Components/NewParagraphForm/NewParagraphForm.js
@@ -1,5 +1,5 @@
import React, { useContext, useState, useEffect } from "react";
-import AppContext from "../../AppContext";
+import URLContext from "../../Context/URLContext";
import axios from "axios";
import LevelStandard from "../LevelStandard/LevelStandard";
import Form from "react-bootstrap/Form";
@@ -9,7 +9,7 @@ import Container from "react-bootstrap/esm/Container";
import Button from "react-bootstrap/Button";
const NewParagraphForm = () => {
- const url = useContext(AppContext);
+ let {url} = useContext(URLContext)
const [selectedLevel, setSelectedLevel] = useState("");
const [selectedStandard, setSelectedStandard] = useState("");
diff --git a/src/Components/Paragraph/ParagraphDetails.js b/src/Components/Paragraph/ParagraphDetails.js
index 2519579..715d49c 100644
--- a/src/Components/Paragraph/ParagraphDetails.js
+++ b/src/Components/Paragraph/ParagraphDetails.js
@@ -1,6 +1,6 @@
import React, { useEffect, useState, useContext } from "react";
import { useNavigate, useParams } from "react-router-dom";
-import AppContext from "../../AppContext";
+import URLContext from "../../Context/URLContext";
import axios from "axios";
import Form from "react-bootstrap/Form";
@@ -10,7 +10,7 @@ import Container from "react-bootstrap/esm/Container";
import Button from "react-bootstrap/Button";
const ParagraphDetails = ({ selectedParagraph }) => {
- const url = useContext(AppContext);
+ let {url} = useContext(URLContext)
const navigate = useNavigate();
const { level, standard, pk } = useParams();
diff --git a/src/Components/StandardsList/StandardsList.js b/src/Components/StandardsList/StandardsList.js
index 2df226d..8af21ae 100644
--- a/src/Components/StandardsList/StandardsList.js
+++ b/src/Components/StandardsList/StandardsList.js
@@ -1,5 +1,5 @@
import React, { useContext, useState, useEffect } from "react";
-import AppContext from "../../AppContext";
+import URLContext from "../../Context/URLContext";
import axios from "axios";
import Button from "react-bootstrap/Button";
import Table from "react-bootstrap/Table";
@@ -9,7 +9,7 @@ import Col from "react-bootstrap/Col";
import LevelsList from "../LevelsList/LevelsList";
const StandardsList = () => {
- const url = useContext(AppContext);
+ let {url} = useContext(URLContext)
const [standards, setStandards] = useState([]);
const [levels, setLevels] = useState([]);
const [editingRow, setEditingRow] = useState(null);
diff --git a/src/Context/AuthContext.js b/src/Context/AuthContext.js
new file mode 100644
index 0000000..2e38272
--- /dev/null
+++ b/src/Context/AuthContext.js
@@ -0,0 +1,96 @@
+import { createContext, useEffect, useState, useContext } from "react";
+import URLContext from "./URLContext";
+import jwt_decode from "jwt-decode";
+import { useNavigate } from "react-router-dom";
+
+const AuthContext = createContext();
+
+export default AuthContext;
+
+export const AuthProvider = ({ children }) => {
+ let { url } = useContext(URLContext);
+
+ let [user, setUser] = useState(() =>
+ localStorage.getItem("authTokens")
+ ? jwt_decode(localStorage.getItem("authTokens"))
+ : null
+ );
+
+ let [authTokens, setAuthTokens] = useState(() =>
+ localStorage.getItem("authTokens")
+ ? JSON.parse(localStorage.getItem("authTokens"))
+ : null
+ );
+
+ let [loading, setLoading] = useState(true);
+
+ let navigate = useNavigate();
+
+ let loginUser = async (e) => {
+ e.preventDefault();
+
+ let response = await fetch(url + "/api/token/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ username: e.target.username.value,
+ password: e.target.password.value,
+ }),
+ });
+ let data = await response.json();
+ if (response.status === 200) {
+ setAuthTokens(data);
+ setUser(jwt_decode(data.access));
+ localStorage.setItem("authTokens", JSON.stringify(data));
+ navigate("/");
+ } else {
+ alert("Login Failed");
+ }
+ };
+
+ let logoutUser = () => {
+ setUser(null);
+ setAuthTokens(null);
+ localStorage.removeItem("authTokens");
+ navigate("/login");
+ };
+
+ //let updateToken = async () => {
+ // let response = await fetch(url + "/api/token/refresh/", {
+ // method: "POST",
+ // headers: {
+ // "Content-Type": "application/json",
+ // },
+ // body: JSON.stringify({
+ // refresh: authTokens.refresh,
+ // }),
+ // });
+ // let data = await response.json();
+ // if (response.status === 200) {
+ // setAuthTokens(data);
+ // setUser(jwt_decode(data.access));
+ // localStorage.setItem("authTokens", JSON.stringify(data));
+ // } else {
+ // logoutUser();
+ // }
+ //};
+//
+ useEffect(() => {
+ if (authTokens) {
+ setUser(jwt_decode(authTokens.access))
+ }
+ setLoading(false);
+ }, [authTokens, loading]);
+
+ let contextData = {
+ user: user,
+ authTokens: authTokens,
+ loginUser: loginUser,
+ logoutUser: logoutUser,
+ };
+ return (
+ {children}
+ );
+};
diff --git a/src/Context/URLContext.js b/src/Context/URLContext.js
new file mode 100644
index 0000000..d364551
--- /dev/null
+++ b/src/Context/URLContext.js
@@ -0,0 +1,14 @@
+import { createContext, useEffect, useState } from "react";
+
+const URLContext = createContext();
+
+export default URLContext;
+
+export const URLProvider = ({ children }) => {
+ const url = "http://localhost:8000/admin_api";
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/Pages/Login.js b/src/Pages/Login.js
new file mode 100644
index 0000000..1415e71
--- /dev/null
+++ b/src/Pages/Login.js
@@ -0,0 +1,12 @@
+import React from 'react'
+import LoginPage from '../Components/LoginPage/LoginPage'
+import Container from 'react-bootstrap/Container'
+const Login = () => {
+ return (
+
+
+
+ )
+}
+
+export default Login
\ No newline at end of file
diff --git a/src/Utiles/AxiosInstence.js b/src/Utiles/AxiosInstence.js
new file mode 100644
index 0000000..b4e0817
--- /dev/null
+++ b/src/Utiles/AxiosInstence.js
@@ -0,0 +1,35 @@
+import axios from "axios";
+import jwt_decode from "jwt-decode";
+import dayjs from "dayjs";
+
+let baseURL = "http://localhost:8000/admin_api";
+
+let authTokens = localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null
+
+const axiosInstance = axios.create({
+ baseURL,
+ headers:{Authorization: `Bearer ${authTokens?.access}`}
+});
+
+axiosInstance.interceptors.request.use(async req => {
+ if(!authTokens){
+ authTokens = localStorage.getItem('authTokens') ? JSON.parse(localStorage.getItem('authTokens')) : null
+ req.headers.Authorization = `Bearer ${authTokens?.access}`
+ }
+
+ const user = jwt_decode(authTokens.access)
+ const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;
+
+ if(!isExpired) return req
+
+ const response = await axios.post(`${baseURL}/api/token/refresh/`, {
+ refresh: authTokens.refresh
+ });
+
+ localStorage.setItem('authTokens', JSON.stringify(response.data))
+ req.headers.Authorization = `Bearer ${response.data.access}`
+ return req
+})
+
+
+export default axiosInstance;
\ No newline at end of file
diff --git a/src/Utiles/PrivateRoute.js b/src/Utiles/PrivateRoute.js
new file mode 100644
index 0000000..46cb33e
--- /dev/null
+++ b/src/Utiles/PrivateRoute.js
@@ -0,0 +1,11 @@
+import { Route, Navigate, Outlet } from "react-router-dom";
+import { useContext } from "react";
+import AuthContext from "../Context/AuthContext";
+
+const PrivateRoute = ({ children, ...rest }) => {
+ let { user } = useContext(AuthContext);
+
+ return user ? : ;
+};
+
+export default PrivateRoute;
diff --git a/src/Utiles/useAxios.js b/src/Utiles/useAxios.js
new file mode 100644
index 0000000..75c303a
--- /dev/null
+++ b/src/Utiles/useAxios.js
@@ -0,0 +1,43 @@
+import axios from 'axios'
+import jwt_decode from "jwt-decode";
+import dayjs from 'dayjs'
+import { useContext } from 'react'
+import AuthContext from '../Context/AuthContext'
+
+
+const baseURL = "http://localhost:8000/admin_api";
+
+
+const useAxios = () => {
+ const {authTokens, setUser, setAuthTokens} = useContext(AuthContext)
+
+ const axiosInstance = axios.create({
+ baseURL,
+ headers:{Authorization: `Bearer ${authTokens?.access}`}
+ });
+
+
+ axiosInstance.interceptors.request.use(async req => {
+
+ const user = jwt_decode(authTokens.access)
+ const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;
+
+ if(!isExpired) return req
+
+ const response = await axios.post(`${baseURL}/api/token/refresh/`, {
+ refresh: authTokens.refresh
+ });
+
+ localStorage.setItem('authTokens', JSON.stringify(response.data))
+
+ setAuthTokens(response.data)
+ setUser(jwt_decode(response.data.access))
+
+ req.headers.Authorization = `Bearer ${response.data.access}`
+ return req
+ })
+
+ return axiosInstance
+}
+
+export default useAxios;
\ No newline at end of file