#13-login-page #19

Merged
juggy1233 merged 4 commits from #13-login-page into master 2023-03-19 14:59:33 -04:00
10 changed files with 145 additions and 25 deletions

View File

@@ -8,8 +8,6 @@ from config import Config
db = SQLAlchemy() db = SQLAlchemy()
migrate = Migrate() migrate = Migrate()
login = LoginManager() login = LoginManager()
login.login_view = "login"
login.login_message = "Please log in to access this page."
def create_app(config_class=Config): def create_app(config_class=Config):

View File

@@ -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 app.bp import bp
from flask import Response, jsonify, request from flask import jsonify, request
from app.errors import error_response from app.errors import error_response
from flask_login import current_user from flask_login import current_user
@@ -20,10 +20,10 @@ def login_route():
if current_user.is_authenticated: if current_user.is_authenticated:
return error_response(400, "A user is already logged in!") return error_response(400, "A user is already logged in!")
if not data.get("user_id") or not data.get("password"): if not data.get("username") or not data.get("password"):
return error_response(400, "Must supply user_id and 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: if not user:
return error_response(400, "User not found") return error_response(400, "User not found")
@@ -36,10 +36,8 @@ def login_route():
@bp.route("/logout", methods=["POST"]) @bp.route("/logout", methods=["POST"])
@login_required
def logout_route(): def logout_route():
if not current_user.is_authenticated:
return error_response(400, "No users are logged in!")
resp = jsonify(current_user.to_dict()) resp = jsonify(current_user.to_dict())
logout_user() logout_user()
return resp return resp

View File

@@ -3,10 +3,12 @@ dotenv.load_dotenv()
from app import create_app, db from app import create_app, db
from app.models import User from app.models import User
from flask_cors import CORS
app = create_app() app = create_app()
CORS(app, supports_credentials=True, resources={r"/.*": {"origins": r".*localhost.*"}})
@app.shell_context_processor @app.shell_context_processor
def make_shell_context(): def make_shell_context():
return {'db': db, 'User': User} return {"db": db, "User": User}

View File

@@ -1,6 +1,7 @@
alembic==1.10.2 alembic==1.10.2
click==8.1.3 click==8.1.3
Flask==2.2.3 Flask==2.2.3
Flask-Cors==3.0.10
Flask-HTTPAuth==4.7.0 Flask-HTTPAuth==4.7.0
Flask-Login==0.6.2 Flask-Login==0.6.2
Flask-Migrate==4.0.4 Flask-Migrate==4.0.4
@@ -12,6 +13,7 @@ Jinja2==3.1.2
Mako==1.2.4 Mako==1.2.4
MarkupSafe==2.1.2 MarkupSafe==2.1.2
python-dotenv==1.0.0 python-dotenv==1.0.0
six==1.16.0
SQLAlchemy==2.0.6 SQLAlchemy==2.0.6
typing_extensions==4.5.0 typing_extensions==4.5.0
Werkzeug==2.2.3 Werkzeug==2.2.3

View File

@@ -2,12 +2,15 @@ import "./App.css";
import { Route } from "wouter"; import { Route } from "wouter";
import HomePage from "./pages/HomePage"; import HomePage from "./pages/HomePage";
import LoginPage from "./pages/LoginPage"; import LoginPage from "./pages/LoginPage";
import { UserContextProvider } from "./contexts/UserContext";
function App() { function App() {
return ( return (
<div className="App"> <div className="App">
<Route path="/" component={HomePage} /> <UserContextProvider>
<Route path="/login" component={LoginPage} /> <Route path="/" component={HomePage} />
<Route path="/login" component={LoginPage} />
</UserContextProvider>
</div> </div>
); );
} }

View File

@@ -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 MyNavbar = () => {
const { currentUser } = useContext(UserContext);
return ( return (
<Navbar variant="dark" bg="dark" expand="lg"> <Navbar variant="dark" bg="dark" expand="lg">
<Container> <Container>
@@ -9,7 +12,9 @@ const MyNavbar = () => {
<Navbar.Collapse id="navbar-nav"> <Navbar.Collapse id="navbar-nav">
<Nav className="ms-auto"> <Nav className="ms-auto">
<Nav.Link href="/">Home</Nav.Link> <Nav.Link href="/">Home</Nav.Link>
<Nav.Link href="/login">Login</Nav.Link> {(currentUser?.id && (
<Nav.Link href="/logout">Logout</Nav.Link>
)) || <Nav.Link href="/login">Login</Nav.Link>}
</Nav> </Nav>
</Navbar.Collapse> </Navbar.Collapse>
</Container> </Container>

View File

@@ -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 (
<UserContext.Provider value={contextValues}>
{children}
</UserContext.Provider>
);
};
export default UserContext;
export { UserContextProvider };

View File

@@ -1,7 +1,17 @@
import { useContext, useEffect } from "react";
import { Container } from "react-bootstrap"; import { Container } from "react-bootstrap";
import MyNavbar from "../components/MyNavbar"; import MyNavbar from "../components/MyNavbar";
import UserContext from "../contexts/UserContext";
const HomePage = () => { const HomePage = () => {
const { currentUser } = useContext(UserContext);
useEffect(() => {
if (!currentUser?.id) {
window.location.replace("/login");
}
}, [currentUser]);
return ( return (
<div> <div>
<MyNavbar /> <MyNavbar />

View File

@@ -1,27 +1,88 @@
import React from "react"; import React, { useContext, useEffect, useState } from "react";
import { Col, Container, Form, Row } from "react-bootstrap"; import { Button, Col, Container, Form, Row, Alert } from "react-bootstrap";
import MyNavbar from "../components/MyNavbar"; import MyNavbar from "../components/MyNavbar";
import UserContext from "../contexts/UserContext";
import { makeRequest } from "../utils.ts";
const LoginPage = () => { 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 ( return (
<React.Fragment> <React.Fragment>
<MyNavbar /> <MyNavbar />
{error && (
<Container>
<Alert variant="danger" className="m-4 mx-auto w-50 text-center">
{error.message}
</Alert>
</Container>
)}
<Container className="p-5"> <Container className="p-5">
<Form> <Form onSubmit={sendLoginRequest}>
<Form.Group as={Row} className="mb-3" controlId="username"> <Form.Group as={Row} className="mb-3" controlId="username">
<Form.Label column sm={2}> <Form.Label column sm={2} className="me-2">
Username Username
</Form.Label> </Form.Label>
<Col sm={10}> <Col sm={9}>
<Form.Control type="username" placeholder="username" /> <Form.Control
type="username"
placeholder="username"
onChange={(e) => {
setUsername(e.target.value);
}}
/>
</Col> </Col>
</Form.Group> </Form.Group>
<Form.Group as={Row} className="mb-3" controlId="password"> <Form.Group as={Row} className="mb-3" controlId="password">
<Form.Label column sm={2}> <Form.Label column sm={2} className="me-2">
Password Password
</Form.Label> </Form.Label>
<Col sm={10}> <Col sm={9}>
<Form.Control type="password" placeholder="password" /> <Form.Control
type="password"
placeholder="password"
onChange={(e) => {
setPassword(e.target.value);
}}
/>
</Col>
</Form.Group>
<Form.Group as={Row} className="mb-3" controlId="submit">
<Col sm={2}></Col>
<Col sm={2}>
<Button type="submit">Submit</Button>
</Col> </Col>
</Form.Group> </Form.Group>
<Form.Group as={Row}> <Form.Group as={Row}>

11
frontend/src/utils.ts Normal file
View File

@@ -0,0 +1,11 @@
const makeRequest = ({ url, method, body }): Promise<Response> => {
return fetch(url, {
method: method,
credentials: "include",
body: JSON.stringify(body),
headers: { "content-type": "application/json" },
mode: "cors",
});
};
export { makeRequest };