diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 072d86e..4debff8 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -8,8 +8,6 @@ from config import Config db = SQLAlchemy() migrate = Migrate() login = LoginManager() -login.login_view = "login" -login.login_message = "Please log in to access this page." def create_app(config_class=Config): diff --git a/backend/app/routes.py b/backend/app/routes.py index e45b6ee..649411a 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -1,6 +1,6 @@ -from flask_login import login_user, logout_user +from flask_login import login_required, login_user, logout_user from app.bp import bp -from flask import Response, jsonify, request +from flask import jsonify, request from app.errors import error_response from flask_login import current_user @@ -20,10 +20,10 @@ def login_route(): if current_user.is_authenticated: return error_response(400, "A user is already logged in!") - if not data.get("user_id") or not data.get("password"): - return error_response(400, "Must supply user_id and password") + if not data.get("username") or not data.get("password"): + return error_response(400, "Must supply username and password") - user = User.query.get(data.get("user_id")) + user = User.query.filter_by(username=data["username"]).first() if not user: return error_response(400, "User not found") @@ -36,10 +36,8 @@ def login_route(): @bp.route("/logout", methods=["POST"]) +@login_required def logout_route(): - if not current_user.is_authenticated: - return error_response(400, "No users are logged in!") - resp = jsonify(current_user.to_dict()) logout_user() return resp diff --git a/backend/main.py b/backend/main.py index 1cbaba5..be9b9c8 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3,10 +3,12 @@ dotenv.load_dotenv() from app import create_app, db from app.models import User +from flask_cors import CORS app = create_app() +CORS(app, supports_credentials=True, resources={r"/.*": {"origins": r".*localhost.*"}}) + @app.shell_context_processor def make_shell_context(): - return {'db': db, 'User': User} - + return {"db": db, "User": User} diff --git a/backend/requirements.txt b/backend/requirements.txt index 06f76c6..6937fee 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,6 +1,7 @@ alembic==1.10.2 click==8.1.3 Flask==2.2.3 +Flask-Cors==3.0.10 Flask-HTTPAuth==4.7.0 Flask-Login==0.6.2 Flask-Migrate==4.0.4 @@ -12,6 +13,7 @@ Jinja2==3.1.2 Mako==1.2.4 MarkupSafe==2.1.2 python-dotenv==1.0.0 +six==1.16.0 SQLAlchemy==2.0.6 typing_extensions==4.5.0 Werkzeug==2.2.3 diff --git a/frontend/src/App.js b/frontend/src/App.js index e77fd2e..7a5767a 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -2,12 +2,15 @@ import "./App.css"; import { Route } from "wouter"; import HomePage from "./pages/HomePage"; import LoginPage from "./pages/LoginPage"; +import { UserContextProvider } from "./contexts/UserContext"; function App() { return (
- - + + + +
); } diff --git a/frontend/src/components/MyNavbar.jsx b/frontend/src/components/MyNavbar.jsx index 658395b..1cd46ff 100644 --- a/frontend/src/components/MyNavbar.jsx +++ b/frontend/src/components/MyNavbar.jsx @@ -1,6 +1,9 @@ -import { Nav, Container, Navbar, NavDropdown } from "react-bootstrap"; +import { useContext } from "react"; +import { Nav, Container, Navbar } from "react-bootstrap"; +import UserContext from "../contexts/UserContext"; const MyNavbar = () => { + const { currentUser } = useContext(UserContext); return ( @@ -9,7 +12,9 @@ const MyNavbar = () => { diff --git a/frontend/src/contexts/UserContext.jsx b/frontend/src/contexts/UserContext.jsx new file mode 100644 index 0000000..c0b3a97 --- /dev/null +++ b/frontend/src/contexts/UserContext.jsx @@ -0,0 +1,30 @@ +import { createContext, useMemo, useState } from "react"; + +const UserContext = createContext({}); + +const UserContextProvider = ({ children }) => { + + const [currentUser, setCurrentUser] = useState(undefined); + + // useMemo does not wait for after render, like useEffect. + useMemo(()=>{ + setCurrentUser(JSON.parse(localStorage.getItem("currentUser"))); + },[]); + + const contextValues = { + currentUser, + setCurrentUser: (data) => { + localStorage.setItem("currentUser", JSON.stringify(data)) + setCurrentUser(data); + }, + }; + + return ( + + {children} + + ); +}; + +export default UserContext; +export { UserContextProvider }; diff --git a/frontend/src/pages/HomePage.jsx b/frontend/src/pages/HomePage.jsx index 527d63a..ebc5b5d 100644 --- a/frontend/src/pages/HomePage.jsx +++ b/frontend/src/pages/HomePage.jsx @@ -1,7 +1,17 @@ +import { useContext, useEffect } from "react"; import { Container } from "react-bootstrap"; import MyNavbar from "../components/MyNavbar"; +import UserContext from "../contexts/UserContext"; const HomePage = () => { + const { currentUser } = useContext(UserContext); + + useEffect(() => { + if (!currentUser?.id) { + window.location.replace("/login"); + } + }, [currentUser]); + return (
diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx index 2699150..21ef318 100644 --- a/frontend/src/pages/LoginPage.jsx +++ b/frontend/src/pages/LoginPage.jsx @@ -1,27 +1,88 @@ -import React from "react"; -import { Col, Container, Form, Row } from "react-bootstrap"; +import React, { useContext, useEffect, useState } from "react"; +import { Button, Col, Container, Form, Row, Alert } from "react-bootstrap"; import MyNavbar from "../components/MyNavbar"; +import UserContext from "../contexts/UserContext"; +import { makeRequest } from "../utils.ts"; const LoginPage = () => { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + + const { currentUser, setCurrentUser } = useContext(UserContext); + + useEffect(() => { + console.log(currentUser); + if (currentUser?.id) { + gotoHome(); + } + }, [currentUser]); + + const gotoHome = () => { + window.location.href = "/"; + }; + + const sendLoginRequest = async (e) => { + e?.preventDefault(); + await makeRequest({ + url: "http://localhost:5000/login", + method: "POST", + body: { username, password }, + }) + .then((resp) => resp.json()) + .then((data) => { + if (data.error) { + setError(data); + return; + } + setCurrentUser(data); + }); + }; + return ( + {error && ( + + + {error.message} + + + )} -
+ - + Username - - + + { + setUsername(e.target.value); + }} + /> - + Password - - + + { + setPassword(e.target.value); + }} + /> + + + + + + diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts new file mode 100644 index 0000000..c7b116f --- /dev/null +++ b/frontend/src/utils.ts @@ -0,0 +1,11 @@ +const makeRequest = ({ url, method, body }): Promise => { + return fetch(url, { + method: method, + credentials: "include", + body: JSON.stringify(body), + headers: { "content-type": "application/json" }, + mode: "cors", + }); +}; + +export { makeRequest };