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",
"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",

View File

@ -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",

View File

@ -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>
);
}

View File

@ -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([]);
let api = useAxios();
let getLevels = async () => {
let response = await api.get("/levels/");
if (response.status === 200) {
setLevels(response.data);
}
};
useEffect(()=> {
axios
.get(url + "/levels/")
.then((response) => setLevels(response.data))
.catch((error) => console.log(error));
}, []);
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>
);
};

View File

@ -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);

View File

@ -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([]);

View File

@ -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);

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

View File

@ -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("");

View File

@ -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();

View File

@ -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);

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;