#13-login-page #19
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
<UserContextProvider>
|
||||||
<Route path="/" component={HomePage} />
|
<Route path="/" component={HomePage} />
|
||||||
<Route path="/login" component={LoginPage} />
|
<Route path="/login" component={LoginPage} />
|
||||||
|
</UserContextProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
30
frontend/src/contexts/UserContext.jsx
Normal file
30
frontend/src/contexts/UserContext.jsx
Normal 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 };
|
||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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
11
frontend/src/utils.ts
Normal 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 };
|
||||||
Reference in New Issue
Block a user