Updated version

This commit is contained in:
louai98 2023-07-31 09:57:40 +02:00
parent 07c8a51ac3
commit 0a3596bbb9
18 changed files with 346 additions and 42 deletions

12
package-lock.json generated
View File

@ -13,6 +13,8 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"dayjs": "^1.11.9",
"jwt-decode": "^3.1.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.7.4", "react-bootstrap": "^2.7.4",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@ -6590,6 +6592,11 @@
"node": ">=10" "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": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -11837,6 +11844,11 @@
"node": ">=4.0" "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": { "node_modules/kind-of": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",

View File

@ -8,6 +8,8 @@
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"dayjs": "^1.11.9",
"jwt-decode": "^3.1.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.7.4", "react-bootstrap": "^2.7.4",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -1,12 +1,14 @@
import logo from "./logo.svg"; import logo from "./logo.svg";
import "./App.css"; 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 Image from "react-bootstrap/Image";
import IddrsImg from "./Static/Imgs/IDDRS.png"; import IddrsImg from "./Static/Imgs/IDDRS.png";
import Container from "react-bootstrap/esm/Container"; import Container from "react-bootstrap/esm/Container";
import AppContext from "./AppContext"; import AppContext from "./AppContext";
import PrivateRoute from "./Utiles/PrivateRoute";
import Navbar_ from "./Components/Navbar/Navbar_"; import Navbar_ from "./Components/Navbar/Navbar_";
import { AuthProvider } from "./Context/AuthContext";
import { URLProvider } from "./Context/URLContext";
import Home from "./Pages/Home"; import Home from "./Pages/Home";
import Levels from "./Pages/Levels"; import Levels from "./Pages/Levels";
@ -14,26 +16,48 @@ import Content from "./Pages/Content";
import NewParagraph from "./Pages/NewParagraph"; import NewParagraph from "./Pages/NewParagraph";
import Standards from "./Pages/Standards"; import Standards from "./Pages/Standards";
import Details from "./Pages/Details"; import Details from "./Pages/Details";
import Login from "./Pages/Login";
function App() { function App() {
const iddrs_url = "http://localhost:8000/admin_api";
return ( return (
<main> <main>
<AppContext.Provider value={iddrs_url}> <URLProvider>
<Container> <AuthProvider>
<Image className="mt-5" src={IddrsImg} alt="IDDRS" fluid /> <Container>
<Navbar_ /> <Image className="mt-5" src={IddrsImg} alt="IDDRS" fluid />
<Routes> <Navbar_ />
<Route path="/" element={<Home />} /> <Routes>
<Route path="/content" element={<Content />} /> <Route exact path="/" element={<PrivateRoute />}>
<Route path="/newparagraph" element={<NewParagraph />} /> <Route exact path="/" element={<Home />} />
<Route path="/levels" element={<Levels />} /> </Route>
<Route path="/standards" element={<Standards />} /> <Route path="/content" element={<PrivateRoute />}>
<Route path="/details/:level/:standard/:pk" element={<Details />} /> <Route path="/content" element={<Content />} />
</Routes> </Route>
</Container> <Route path="/newparagraph" element={<PrivateRoute />}>
</AppContext.Provider> <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>
<Route
path="/details/:level/:standard/:pk"
element={<PrivateRoute />}
>
<Route
path="/details/:level/:standard/:pk"
element={<Details />}
/>
</Route>
<Route path="/login" element={<Login />} />
</Routes>
</Container>
</AuthProvider>
</URLProvider>
</main> </main>
); );
} }

View File

@ -1,12 +1,13 @@
import React, { useContext, useState, useEffect } from "react"; import React, { useContext, useState, useEffect } from "react";
import AppContext from "../../AppContext"; import URLContext from "../../Context/URLContext";
import Form from "react-bootstrap/Form"; import Form from "react-bootstrap/Form";
import Container from "react-bootstrap/Container"; import Container from "react-bootstrap/Container";
import axios from "axios"; import axios from "axios";
import ContentList from "./ContentList"; import ContentList from "./ContentList";
import useAxios from "../../Utiles/useAxios";
const ContentComp = () => { const ContentComp = () => {
const url = useContext(AppContext); let { url } = useContext(URLContext);
const [levels, setLevels] = useState([]); const [levels, setLevels] = useState([]);
const [standards, setStandards] = useState([]); const [standards, setStandards] = useState([]);
@ -19,12 +20,19 @@ const ContentComp = () => {
const [data, setData] = useState([]); const [data, setData] = useState([]);
useEffect(() => { let api = useAxios();
axios
.get(url + "/levels/") let getLevels = async () => {
.then((response) => setLevels(response.data)) let response = await api.get("/levels/");
.catch((error) => console.log(error)); if (response.status === 200) {
}, []); setLevels(response.data);
}
};
useEffect(()=> {
getLevels()
}, [])
useEffect(() => { useEffect(() => {
axios axios
@ -60,7 +68,6 @@ const ContentComp = () => {
return ( return (
<Container className="mt-5"> <Container className="mt-5">
<Form.Select <Form.Select
aria-label="Default select example" aria-label="Default select example"
onChange={handleLevelChange} onChange={handleLevelChange}
@ -87,7 +94,11 @@ const ContentComp = () => {
))} ))}
</Form.Select> </Form.Select>
)} )}
<ContentList standardContnet = {data} level ={selectedLevel} standard = {selectedStandard} /> <ContentList
standardContnet={data}
level={selectedLevel}
standard={selectedStandard}
/>
</Container> </Container>
); );
}; };

View File

@ -1,5 +1,5 @@
import React, { useContext, useState, useEffect } from "react"; import React, { useContext, useState, useEffect } from "react";
import AppContext from "../../AppContext"; import URLContext from "../../Context/URLContext";
import Button from "react-bootstrap/esm/Button"; import Button from "react-bootstrap/esm/Button";
import axios from "axios"; import axios from "axios";
import Row from "react-bootstrap/Row"; import Row from "react-bootstrap/Row";
@ -8,7 +8,7 @@ import Classes from "./HomePage.module.css";
import ClipLoader from "react-spinners/ClipLoader"; import ClipLoader from "react-spinners/ClipLoader";
const HomePage = () => { const HomePage = () => {
const url = useContext(AppContext); let {url} = useContext(URLContext)
const [newContentTracker, setNewContentTracker] = useState([]); const [newContentTracker, setNewContentTracker] = useState([]);
const [newContents, setNewContents] = useState(true); const [newContents, setNewContents] = useState(true);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);

View File

@ -1,10 +1,10 @@
import React, { useContext, useState, useEffect } from "react"; import React, { useContext, useState, useEffect } from "react";
import AppContext from "../../AppContext"; import URLContext from "../../Context/URLContext";
import axios from "axios"; import axios from "axios";
import Form from "react-bootstrap/Form"; import Form from "react-bootstrap/Form";
const LevelStandard = ({ onValueChange }) => { const LevelStandard = ({ onValueChange }) => {
const url = useContext(AppContext); let {url} = useContext(URLContext)
const [levels, setLevels] = useState([]); const [levels, setLevels] = useState([]);
const [standards, setStandards] = useState([]); const [standards, setStandards] = useState([]);

View File

@ -1,5 +1,5 @@
import React, { useContext, useState, useEffect } from "react"; import React, { useContext, useState, useEffect } from "react";
import AppContext from "../../AppContext"; import URLContext from "../../Context/URLContext";
import axios from "axios"; import axios from "axios";
import Button from "react-bootstrap/Button"; import Button from "react-bootstrap/Button";
import Table from "react-bootstrap/Table"; import Table from "react-bootstrap/Table";
@ -8,7 +8,7 @@ import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col"; import Col from "react-bootstrap/Col";
const LevelsList = () => { const LevelsList = () => {
const url = useContext(AppContext); let { url } = useContext(URLContext);
const [editingRow, setEditingRow] = useState(null); const [editingRow, setEditingRow] = useState(null);
const [levels, setLevels] = useState([]); const [levels, setLevels] = useState([]);
@ -32,7 +32,6 @@ const LevelsList = () => {
}) })
.then((response) => { .then((response) => {
console.log("Form submitted successfully:", response.data); console.log("Form submitted successfully:", response.data);
}) })
.catch((error) => { .catch((error) => {
console.error("Error submitting form:", error); console.error("Error submitting form:", error);

View 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;

View File

@ -1,11 +1,14 @@
import React from "react"; import React, {useContext} from "react";
import Nav from "react-bootstrap/Nav"; import Nav from "react-bootstrap/Nav";
import Navbar from "react-bootstrap/Navbar"; import Navbar from "react-bootstrap/Navbar";
import Container from "react-bootstrap/esm/Container"; import Container from "react-bootstrap/esm/Container";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import classes from "./Navbar_.module.css"; import classes from "./Navbar_.module.css";
import AuthContext from "../../Context/AuthContext";
import Button from "react-bootstrap/esm/Button";
const Navbar_ = () => { const Navbar_ = () => {
let {user, logoutUser} = useContext(AuthContext);
return ( return (
<Navbar className="mt-5" bg="light" expand="lg"> <Navbar className="mt-5" bg="light" expand="lg">
<Container> <Container>
@ -52,6 +55,13 @@ const Navbar_ = () => {
> >
Standards Standards
</NavLink> </NavLink>
{user && <p>Hello { user.username }</p>}
{user && (
<Button onClick={logoutUser} variant="primary"> Logout </Button>
)}
</Nav> </Nav>
</Navbar.Collapse> </Navbar.Collapse>
</Container> </Container>

View File

@ -1,5 +1,5 @@
import React, { useContext, useState, useEffect } from "react"; import React, { useContext, useState, useEffect } from "react";
import AppContext from "../../AppContext"; import URLContext from "../../Context/URLContext";
import axios from "axios"; import axios from "axios";
import LevelStandard from "../LevelStandard/LevelStandard"; import LevelStandard from "../LevelStandard/LevelStandard";
import Form from "react-bootstrap/Form"; import Form from "react-bootstrap/Form";
@ -9,7 +9,7 @@ import Container from "react-bootstrap/esm/Container";
import Button from "react-bootstrap/Button"; import Button from "react-bootstrap/Button";
const NewParagraphForm = () => { const NewParagraphForm = () => {
const url = useContext(AppContext); let {url} = useContext(URLContext)
const [selectedLevel, setSelectedLevel] = useState(""); const [selectedLevel, setSelectedLevel] = useState("");
const [selectedStandard, setSelectedStandard] = useState(""); const [selectedStandard, setSelectedStandard] = useState("");

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState, useContext } from "react"; import React, { useEffect, useState, useContext } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import AppContext from "../../AppContext"; import URLContext from "../../Context/URLContext";
import axios from "axios"; import axios from "axios";
import Form from "react-bootstrap/Form"; import Form from "react-bootstrap/Form";
@ -10,7 +10,7 @@ import Container from "react-bootstrap/esm/Container";
import Button from "react-bootstrap/Button"; import Button from "react-bootstrap/Button";
const ParagraphDetails = ({ selectedParagraph }) => { const ParagraphDetails = ({ selectedParagraph }) => {
const url = useContext(AppContext); let {url} = useContext(URLContext)
const navigate = useNavigate(); const navigate = useNavigate();
const { level, standard, pk } = useParams(); const { level, standard, pk } = useParams();

View File

@ -1,5 +1,5 @@
import React, { useContext, useState, useEffect } from "react"; import React, { useContext, useState, useEffect } from "react";
import AppContext from "../../AppContext"; import URLContext from "../../Context/URLContext";
import axios from "axios"; import axios from "axios";
import Button from "react-bootstrap/Button"; import Button from "react-bootstrap/Button";
import Table from "react-bootstrap/Table"; import Table from "react-bootstrap/Table";
@ -9,7 +9,7 @@ import Col from "react-bootstrap/Col";
import LevelsList from "../LevelsList/LevelsList"; import LevelsList from "../LevelsList/LevelsList";
const StandardsList = () => { const StandardsList = () => {
const url = useContext(AppContext); let {url} = useContext(URLContext)
const [standards, setStandards] = useState([]); const [standards, setStandards] = useState([]);
const [levels, setLevels] = useState([]); const [levels, setLevels] = useState([]);
const [editingRow, setEditingRow] = useState(null); const [editingRow, setEditingRow] = useState(null);

View 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
View 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
View 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

View 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;

View 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
View 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;