Updated version
This commit is contained in:
parent
07c8a51ac3
commit
0a3596bbb9
12
package-lock.json
generated
12
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
40
src/App.js
40
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 (
|
||||
<main>
|
||||
<AppContext.Provider value={iddrs_url}>
|
||||
<URLProvider>
|
||||
<AuthProvider>
|
||||
<Container>
|
||||
<Image className="mt-5" src={IddrsImg} alt="IDDRS" fluid />
|
||||
<Navbar_ />
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route exact path="/" element={<PrivateRoute />}>
|
||||
<Route exact path="/" element={<Home />} />
|
||||
</Route>
|
||||
<Route path="/content" element={<PrivateRoute />}>
|
||||
<Route path="/content" element={<Content />} />
|
||||
</Route>
|
||||
<Route path="/newparagraph" element={<PrivateRoute />}>
|
||||
<Route path="/newparagraph" element={<NewParagraph />} />
|
||||
</Route>
|
||||
<Route path="/levels" element={<PrivateRoute />}>
|
||||
<Route path="/levels" element={<Levels />} />
|
||||
</Route>
|
||||
<Route path="/standards" element={<PrivateRoute />}>
|
||||
<Route path="/standards" element={<Standards />} />
|
||||
<Route path="/details/:level/:standard/:pk" element={<Details />} />
|
||||
</Route>
|
||||
<Route
|
||||
|
||||
path="/details/:level/:standard/:pk"
|
||||
element={<PrivateRoute />}
|
||||
>
|
||||
<Route
|
||||
path="/details/:level/:standard/:pk"
|
||||
element={<Details />}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Routes>
|
||||
</Container>
|
||||
</AppContext.Provider>
|
||||
</AuthProvider>
|
||||
</URLProvider>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
@ -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 (
|
||||
<Container className="mt-5">
|
||||
|
||||
<Form.Select
|
||||
aria-label="Default select example"
|
||||
onChange={handleLevelChange}
|
||||
@ -87,7 +94,11 @@ const ContentComp = () => {
|
||||
))}
|
||||
</Form.Select>
|
||||
)}
|
||||
<ContentList standardContnet = {data} level ={selectedLevel} standard = {selectedStandard} />
|
||||
<ContentList
|
||||
standardContnet={data}
|
||||
level={selectedLevel}
|
||||
standard={selectedStandard}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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([]);
|
||||
|
@ -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);
|
||||
|
35
src/Components/LoginPage/LoginPage.js
Normal file
35
src/Components/LoginPage/LoginPage.js
Normal file
@ -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 (
|
||||
<div>
|
||||
<Form onSubmit={loginUser}>
|
||||
<Form.Group className="mb-3" controlId="formGroupEmail">
|
||||
<Form.Label>Email address</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
name="username"
|
||||
placeholder="Enter Username"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group className="mb-3" controlId="formGroupPassword">
|
||||
<Form.Label>Password</Form.Label>
|
||||
<Form.Control
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button variant="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
@ -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 (
|
||||
<Navbar className="mt-5" bg="light" expand="lg">
|
||||
<Container>
|
||||
@ -52,6 +55,13 @@ const Navbar_ = () => {
|
||||
>
|
||||
Standards
|
||||
</NavLink>
|
||||
|
||||
{user && <p>Hello { user.username }</p>}
|
||||
|
||||
{user && (
|
||||
<Button onClick={logoutUser} variant="primary"> Logout </Button>
|
||||
)}
|
||||
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
|
@ -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("");
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
96
src/Context/AuthContext.js
Normal file
96
src/Context/AuthContext.js
Normal file
@ -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 (
|
||||
<AuthContext.Provider value={contextData}>{children}</AuthContext.Provider>
|
||||
);
|
||||
};
|
14
src/Context/URLContext.js
Normal file
14
src/Context/URLContext.js
Normal file
@ -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 (
|
||||
<URLContext.Provider value={{ 'url': url }}>
|
||||
{children}
|
||||
</URLContext.Provider>
|
||||
);
|
||||
};
|
12
src/Pages/Login.js
Normal file
12
src/Pages/Login.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import LoginPage from '../Components/LoginPage/LoginPage'
|
||||
import Container from 'react-bootstrap/Container'
|
||||
const Login = () => {
|
||||
return (
|
||||
<Container>
|
||||
<LoginPage />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
35
src/Utiles/AxiosInstence.js
Normal file
35
src/Utiles/AxiosInstence.js
Normal file
@ -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;
|
11
src/Utiles/PrivateRoute.js
Normal file
11
src/Utiles/PrivateRoute.js
Normal file
@ -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 ? <Outlet /> : <Navigate to="/login" />;
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
43
src/Utiles/useAxios.js
Normal file
43
src/Utiles/useAxios.js
Normal file
@ -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;
|
Loading…
Reference in New Issue
Block a user