:3
This commit is contained in:
parent
cd99fea483
commit
81e68770c6
1
.env
Normal file
1
.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL="postgres://postgres:postgres@127.0.0.1:5432/persmgr""
|
1379
Cargo.lock
generated
1379
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -9,9 +9,13 @@ askama = "0.14.0"
|
||||||
axum = "0.8.4"
|
axum = "0.8.4"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.143"
|
serde_json = "1.0.143"
|
||||||
|
sqlx = { version = "0.8.6", features = ["macros", "postgres", "runtime-tokio"] }
|
||||||
tokio = { version = "1.47.1", features = ["full"] }
|
tokio = { version = "1.47.1", features = ["full"] }
|
||||||
toml = "0.9.5"
|
toml = "0.9.5"
|
||||||
tower = { version = "0.5.2", features = ["full"] }
|
tower = { version = "0.5.2", features = ["full"] }
|
||||||
|
tower-cookies = "0.11.0"
|
||||||
tower-http = { version = "0.6.6", features = ["full"] }
|
tower-http = { version = "0.6.6", features = ["full"] }
|
||||||
|
tower-livereload = "0.9.6"
|
||||||
|
tower-sessions = "0.14.0"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.20"
|
tracing-subscriber = "0.3.20"
|
||||||
|
|
4
build.rs
Normal file
4
build.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=res");
|
||||||
|
println!("cargo:rerun-if-changed=templates");
|
||||||
|
}
|
3
migrations/20250906104612_users.down.sql
Normal file
3
migrations/20250906104612_users.down.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
-- Add down migration script here
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS users;
|
12
migrations/20250906104612_users.up.sql
Normal file
12
migrations/20250906104612_users.up.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
-- Add up migration script here
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
verified_email BOOLEAN NOT NULL,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
pw_hash TEXT NOT NULL,
|
||||||
|
pw_salt TEXT NOT NULL,
|
||||||
|
pfp_id BIGINT NOT NULL
|
||||||
|
);
|
||||||
|
|
|
@ -39,6 +39,9 @@ body {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#topnav_login {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.topnav_button:hover {
|
.topnav_button:hover {
|
||||||
background: var(--bg-color)
|
background: var(--bg-color)
|
||||||
|
@ -47,7 +50,7 @@ body {
|
||||||
#topnav_profile {
|
#topnav_profile {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: right;
|
justify-content: right;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const is_logged_in = false;
|
||||||
|
if (is_logged_in) {
|
||||||
|
const prof = document.getElementById("topnav_profile");
|
||||||
|
const login = document.getElementById("topnav_login");
|
||||||
|
prof.style.display = "flex";
|
||||||
|
login.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
if (event.key === "Escape") on_pfp_click(true);
|
if (event.key === "Escape") on_pfp_click(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// :3
|
||||||
|
|
||||||
function on_pfp_click(close = false) {
|
function on_pfp_click(close = false) {
|
||||||
const el = document.getElementById("topnav_profile_dropdown");
|
const el = document.getElementById("topnav_profile_dropdown");
|
||||||
|
@ -13,3 +24,5 @@ function on_pfp_click(close = false) {
|
||||||
el.style.display = "none";
|
el.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
use axum::{Router, http::StatusCode, routing::get};
|
use crate::db::Database;
|
||||||
|
use axum::{Router, extract::State, http::StatusCode, routing::get};
|
||||||
|
|
||||||
async fn root() -> (StatusCode, &'static str) {
|
pub mod user;
|
||||||
|
|
||||||
|
async fn root(State(state): State<Database>) -> (StatusCode, &'static str) {
|
||||||
(StatusCode::OK, "We Good twin :3c")
|
(StatusCode::OK, "We Good twin :3c")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_routes() -> Router {
|
pub fn register_routes() -> Router<Database> {
|
||||||
let router = Router::new().route("/", get(root));
|
let router = Router::new()
|
||||||
|
.route("/", get(root))
|
||||||
|
.nest("/user", user::register_routes());
|
||||||
|
|
||||||
Router::new().nest("/api", router)
|
Router::new().nest("/api", router)
|
||||||
}
|
}
|
||||||
|
|
17
src/api/user/login.rs
Normal file
17
src/api/user/login.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
extract::State,
|
||||||
|
http::{HeaderMap, HeaderValue, StatusCode},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::db::Database;
|
||||||
|
|
||||||
|
pub async fn route(State(db): State<Database>) -> Response {
|
||||||
|
Response::builder()
|
||||||
|
.header("Location", "/")
|
||||||
|
.header("Set-Cookie", &format!("session=meowmeowmeow"))
|
||||||
|
.status(StatusCode::SEE_OTHER)
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap()
|
||||||
|
}
|
15
src/api/user/mod.rs
Normal file
15
src/api/user/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use axum::{
|
||||||
|
Router,
|
||||||
|
routing::{get, post},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::db::Database;
|
||||||
|
|
||||||
|
pub mod login;
|
||||||
|
pub mod register;
|
||||||
|
|
||||||
|
pub fn register_routes() -> Router<Database> {
|
||||||
|
Router::new()
|
||||||
|
.route("/register", post(register::route))
|
||||||
|
.route("/login", post(login::route))
|
||||||
|
}
|
17
src/api/user/register.rs
Normal file
17
src/api/user/register.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
extract::State,
|
||||||
|
http::{HeaderMap, HeaderValue, StatusCode},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::db::Database;
|
||||||
|
|
||||||
|
pub async fn route(State(db): State<Database>) -> Response {
|
||||||
|
Response::builder()
|
||||||
|
.header("Location", "/")
|
||||||
|
.header("Set-Cookie", &format!("session=meowmeowmeow"))
|
||||||
|
.status(StatusCode::SEE_OTHER)
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap()
|
||||||
|
}
|
23
src/db/mod.rs
Normal file
23
src/db/mod.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use sqlx::{Pool, Postgres, postgres::PgPoolOptions};
|
||||||
|
|
||||||
|
pub mod tables;
|
||||||
|
|
||||||
|
pub type CurrPool = Pool<Postgres>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Database {
|
||||||
|
pool: CurrPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
pub async fn connect() -> Result<Self> {
|
||||||
|
let url = "postgres://postgres:postgres@127.0.0.1:5432/persmgr";
|
||||||
|
let pool = PgPoolOptions::new().connect(url).await?;
|
||||||
|
Ok(Self { pool })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pool(&self) -> &CurrPool {
|
||||||
|
&self.pool
|
||||||
|
}
|
||||||
|
}
|
1
src/db/tables/mod.rs
Normal file
1
src/db/tables/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod user;
|
135
src/db/tables/user.rs
Normal file
135
src/db/tables/user.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
use anyhow::bail;
|
||||||
|
|
||||||
|
use crate::db::CurrPool;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: i64,
|
||||||
|
pub email: String,
|
||||||
|
pub verified_email: bool,
|
||||||
|
pub username: String,
|
||||||
|
pub pw_hash: String,
|
||||||
|
pub pw_salt: String,
|
||||||
|
pub pfp_id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
pub async fn get_by_id(pool: &CurrPool, id: i64) -> anyhow::Result<Self> {
|
||||||
|
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
pub async fn get_by_email(pool: &CurrPool, email: String) -> anyhow::Result<Self> {
|
||||||
|
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE email = $1", email)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
pub async fn get_by_username(pool: &CurrPool, username: String) -> anyhow::Result<Self> {
|
||||||
|
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE username = $1", username)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
pub async fn insert_new(&self, pool: &CurrPool) -> anyhow::Result<()> {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"
|
||||||
|
INSERT INTO users (email, username, pw_hash, pw_salt)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
"#,
|
||||||
|
self.email,
|
||||||
|
self.username,
|
||||||
|
self.pw_hash,
|
||||||
|
self.pw_salt
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_username(
|
||||||
|
&mut self,
|
||||||
|
pool: &CurrPool,
|
||||||
|
username: String,
|
||||||
|
) -> anyhow::Result<&mut Self> {
|
||||||
|
let new_user = sqlx::query_as!(
|
||||||
|
User,
|
||||||
|
"UPDATE users SET username = $1 WHERE id = $2 RETURNING *",
|
||||||
|
username,
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
*self = new_user;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_email(&mut self, pool: &CurrPool, email: String) -> anyhow::Result<&mut Self> {
|
||||||
|
let new_user = sqlx::query_as!(
|
||||||
|
User,
|
||||||
|
"UPDATE users SET email = $1 WHERE id = $2 RETURNING *",
|
||||||
|
email,
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
*self = new_user;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_pw_hash(
|
||||||
|
&mut self,
|
||||||
|
pool: &CurrPool,
|
||||||
|
pw_hash: String,
|
||||||
|
) -> anyhow::Result<&mut Self> {
|
||||||
|
let new_user = sqlx::query_as!(
|
||||||
|
User,
|
||||||
|
"UPDATE users SET pw_hash = $1 WHERE id = $2 RETURNING *",
|
||||||
|
pw_hash,
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
*self = new_user;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
pub async fn set_pw_salt(
|
||||||
|
&mut self,
|
||||||
|
pool: &CurrPool,
|
||||||
|
pw_salt: String,
|
||||||
|
) -> anyhow::Result<&mut Self> {
|
||||||
|
let new_user = sqlx::query_as!(
|
||||||
|
User,
|
||||||
|
"UPDATE users SET pw_salt = $1 WHERE id = $2 RETURNING *",
|
||||||
|
pw_salt,
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
*self = new_user;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_email_verification_status(
|
||||||
|
&mut self,
|
||||||
|
pool: &CurrPool,
|
||||||
|
status: bool,
|
||||||
|
) -> anyhow::Result<&mut Self> {
|
||||||
|
let new_user = sqlx::query_as!(
|
||||||
|
User,
|
||||||
|
"UPDATE users SET verified_email = $1 WHERE id = $2 RETURNING *",
|
||||||
|
status,
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
*self = new_user;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
22
src/main.rs
22
src/main.rs
|
@ -1,10 +1,13 @@
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
|
use tower_cookies::cookie::time::Duration;
|
||||||
use tower_http::{services::ServeDir, trace::TraceLayer};
|
use tower_http::{services::ServeDir, trace::TraceLayer};
|
||||||
|
use tower_sessions::{MemoryStore, SessionManagerLayer};
|
||||||
use tracing::{info, level_filters::LevelFilter};
|
use tracing::{info, level_filters::LevelFilter};
|
||||||
use tracing_subscriber::FmtSubscriber;
|
use tracing_subscriber::FmtSubscriber;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
|
mod db;
|
||||||
mod pages;
|
mod pages;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -14,13 +17,28 @@ async fn main() {
|
||||||
.finish();
|
.finish();
|
||||||
tracing::subscriber::set_global_default(sub).unwrap();
|
tracing::subscriber::set_global_default(sub).unwrap();
|
||||||
|
|
||||||
|
info!("Connecting to DB");
|
||||||
|
|
||||||
|
let pool = db::Database::connect().await.unwrap();
|
||||||
|
|
||||||
info!("Server starting");
|
info!("Server starting");
|
||||||
|
|
||||||
|
let session_store = MemoryStore::default();
|
||||||
|
let session_layer = SessionManagerLayer::new(session_store)
|
||||||
|
.with_secure(false)
|
||||||
|
.with_expiry(tower_sessions::Expiry::OnInactivity(Duration::days(30)));
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.merge(pages::register_routes())
|
|
||||||
.merge(api::register_routes())
|
.merge(api::register_routes())
|
||||||
|
.merge(pages::register_routes())
|
||||||
|
.with_state(pool)
|
||||||
.fallback_service(ServiceBuilder::new().service(ServeDir::new("res")))
|
.fallback_service(ServiceBuilder::new().service(ServeDir::new("res")))
|
||||||
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()));
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.layer(tower_livereload::LiveReloadLayer::new())
|
||||||
|
.layer(session_layer),
|
||||||
|
);
|
||||||
|
|
||||||
let laddr = "0.0.0.0:8080";
|
let laddr = "0.0.0.0:8080";
|
||||||
|
|
||||||
|
|
12
src/pages/login.rs
Normal file
12
src/pages/login.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use askama::Template;
|
||||||
|
use axum::{http::StatusCode, response::Html};
|
||||||
|
|
||||||
|
#[derive(Debug, Template, Clone)]
|
||||||
|
#[template(path = "login.html")]
|
||||||
|
pub struct PageTemplate {}
|
||||||
|
|
||||||
|
pub async fn page() -> (StatusCode, Html<String>) {
|
||||||
|
let page = PageTemplate {};
|
||||||
|
|
||||||
|
(StatusCode::OK, Html(page.render().unwrap()))
|
||||||
|
}
|
|
@ -1,14 +1,20 @@
|
||||||
use axum::{Router, routing::get};
|
use axum::{Router, routing::get};
|
||||||
|
|
||||||
|
use crate::db;
|
||||||
|
|
||||||
pub mod documents;
|
pub mod documents;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
pub mod login;
|
||||||
|
pub mod register;
|
||||||
pub mod roster;
|
pub mod roster;
|
||||||
|
|
||||||
pub fn register_routes() -> Router {
|
pub fn register_routes() -> Router<db::Database> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(index::page))
|
.route("/", get(index::page))
|
||||||
.route("/roster", get(roster::page))
|
.route("/roster", get(roster::page))
|
||||||
.route("/events", get(events::page))
|
.route("/events", get(events::page))
|
||||||
.route("/documents", get(documents::page))
|
.route("/documents", get(documents::page))
|
||||||
|
.route("/login", get(login::page))
|
||||||
|
.route("/register", get(register::page))
|
||||||
}
|
}
|
||||||
|
|
12
src/pages/register.rs
Normal file
12
src/pages/register.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use askama::Template;
|
||||||
|
use axum::{http::StatusCode, response::Html};
|
||||||
|
|
||||||
|
#[derive(Debug, Template, Clone)]
|
||||||
|
#[template(path = "register.html")]
|
||||||
|
pub struct PageTemplate {}
|
||||||
|
|
||||||
|
pub async fn page() -> (StatusCode, Html<String>) {
|
||||||
|
let page = PageTemplate {};
|
||||||
|
|
||||||
|
(StatusCode::OK, Html(page.render().unwrap()))
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
<link href="css/global.css" rel="stylesheet">
|
<link href="css/global.css" rel="stylesheet">
|
||||||
<script src="/js/global.js"></script>
|
<script src="/js/global.js" defer></script>
|
||||||
{% block headers %}{% endblock %}
|
{% block headers %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -25,6 +25,9 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<button id="topnav_login" class="topnav_button" onclick="location.href='/login'">Login</button>
|
||||||
|
|
||||||
|
|
||||||
<div id="topnav_profile">
|
<div id="topnav_profile">
|
||||||
<span>MCorange</span>
|
<span>MCorange</span>
|
||||||
<img src="/img/default_pfp.png" alt="Profile Picture" onclick="on_pfp_click()">
|
<img src="/img/default_pfp.png" alt="Profile Picture" onclick="on_pfp_click()">
|
||||||
|
|
26
templates/login.html
Normal file
26
templates/login.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Login{% endblock %}
|
||||||
|
{% block headers %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Login</h1>
|
||||||
|
<form action="/api/user/login" method="POST">
|
||||||
|
<div>
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input type="text" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
<div>
|
||||||
|
No account yet?
|
||||||
|
<a href="/register">Register here!</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
71
templates/register.html
Normal file
71
templates/register.html
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Register{% endblock %}
|
||||||
|
{% block headers %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Login</h1>
|
||||||
|
<form action="/api/user/register" method="POST">
|
||||||
|
<div>
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<input type="text" id="email" name="email">
|
||||||
|
<span id="email_error" style="color:red; margin-left:5px;"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input type="text" id="username" name="username">
|
||||||
|
<span id="username_error" style="color:red; margin-left:5px;"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="username">Password:</label>
|
||||||
|
<input type="password" id="password" name="password">
|
||||||
|
<span id="password_error" style="color:red; margin-left:5px;"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" id="password2">
|
||||||
|
<span id="confirm_error" style="color:red; margin-left:5px;"></span>
|
||||||
|
</div>
|
||||||
|
<button type="submit" id="submit_btn">Register</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const password = document.getElementById('password');
|
||||||
|
const password2 = document.getElementById('password2');
|
||||||
|
const submit_btn = document.getElementById('submit_btn');
|
||||||
|
const username = document.getElementById('username');
|
||||||
|
const email = document.getElementById('email');
|
||||||
|
|
||||||
|
// const password_error = document.getElementById('password_error');
|
||||||
|
// const password2_error = document.getElementById('confirm_error');
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
submit_btn.disabled = false;
|
||||||
|
if (!(password.value && password2.value) || password.value !== password2.value) {
|
||||||
|
submit_btn.disabled = true;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
if (password.length < 8) {
|
||||||
|
submit_btn.disabled = true;
|
||||||
|
}
|
||||||
|
if (username.length < 3) {
|
||||||
|
submit_btn.disabled = true;
|
||||||
|
}
|
||||||
|
if (email.length < 5) {
|
||||||
|
submit_btn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check on every key press
|
||||||
|
password.addEventListener('input', validate);
|
||||||
|
confirmPassword.addEventListener('input', validate);
|
||||||
|
username.addEventListener("input", validate);
|
||||||
|
email.addEventListener("input", validate);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user