Create a login page
This commit is contained in:
parent
062a749d59
commit
db8cb0efff
7 changed files with 170 additions and 3 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -68,9 +68,17 @@ dependencies = [
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"api",
|
"api",
|
||||||
"console_log",
|
"console_log",
|
||||||
|
"gloo-net",
|
||||||
|
"gloo-storage",
|
||||||
|
"gloo-utils",
|
||||||
"log",
|
"log",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
"yew",
|
"yew",
|
||||||
"yew-router",
|
"yew-router",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,16 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.71"
|
||||||
api = { version = "0.1.0", path = "../api" }
|
api = { version = "0.1.0", path = "../api" }
|
||||||
console_log = { version = "1.0.0", features = ["color"] }
|
console_log = { version = "1.0.0", features = ["color"] }
|
||||||
|
gloo-net = "0.2.6"
|
||||||
|
gloo-storage = "0.2.2"
|
||||||
|
gloo-utils = "0.1.6"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
serde_json = "1.0.96"
|
||||||
|
wasm-bindgen = "0.2.86"
|
||||||
|
wasm-bindgen-futures = "0.4.36"
|
||||||
|
web-sys = "0.3.63"
|
||||||
yew = { version = "0.20.0", features = ["csr"] }
|
yew = { version = "0.20.0", features = ["csr"] }
|
||||||
yew-router = "0.17.0"
|
yew-router = "0.17.0"
|
||||||
|
|
|
||||||
3
app/Trunk.toml
Normal file
3
app/Trunk.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[[hooks]]
|
||||||
|
stage = "build"
|
||||||
|
command = "./dl_bootstrap.sh"
|
||||||
17
app/dl_bootstrap.sh
Executable file
17
app/dl_bootstrap.sh
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
VERSION=5.3.0-alpha3
|
||||||
|
URL=https://github.com/twbs/bootstrap/releases/download/v${VERSION}/bootstrap-${VERSION}-dist.zip
|
||||||
|
|
||||||
|
if [[ ! -d "$TRUNK_DIST_DIR/bootstrap" ]]; then
|
||||||
|
cd "$TRUNK_STAGING_DIR" || {
|
||||||
|
echo "Can't cd to staging directory"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
wget "$URL"
|
||||||
|
unzip bootstrap-*.zip
|
||||||
|
rm bootstrap-*.zip
|
||||||
|
mv bootstrap-* bootstrap
|
||||||
|
else
|
||||||
|
cp -r "$TRUNK_DIST_DIR/bootstrap" "$TRUNK_STAGING_DIR"
|
||||||
|
fi
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html data-bs-theme="dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
<link data-trunk rel="copy-file" href="static/login.css" />
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
100
app/src/main.rs
100
app/src/main.rs
|
|
@ -1,4 +1,8 @@
|
||||||
|
use api::{LoginRequest, LoginResponse};
|
||||||
|
use gloo_storage::{LocalStorage, Storage, errors::StorageError};
|
||||||
use log::Level;
|
use log::Level;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::HtmlInputElement;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
|
@ -30,7 +34,7 @@ fn switch(route: Route) -> Html {
|
||||||
<Index />
|
<Index />
|
||||||
},
|
},
|
||||||
Route::Login => html! {
|
Route::Login => html! {
|
||||||
"Login"
|
<Login />
|
||||||
},
|
},
|
||||||
Route::NotFound => html! {
|
Route::NotFound => html! {
|
||||||
"Page not found"
|
"Page not found"
|
||||||
|
|
@ -40,7 +44,11 @@ fn switch(route: Route) -> Html {
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
fn Index() -> Html {
|
fn Index() -> Html {
|
||||||
let token = use_state(|| None::<String>);
|
let token = use_state(|| match LocalStorage::get::<String>("token") {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(StorageError::KeyNotFound(_)) => None,
|
||||||
|
Err(e) => unreachable!("Could not get token: {e:?}"),
|
||||||
|
});
|
||||||
|
|
||||||
match &*token {
|
match &*token {
|
||||||
Some(_) => html! {
|
Some(_) => html! {
|
||||||
|
|
@ -52,6 +60,94 @@ fn Index() -> Html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn do_login(username: String, password: String) -> anyhow::Result<()> {
|
||||||
|
let rsp = gloo_net::http::Request::post("http://localhost:8085/api/login")
|
||||||
|
.json(&LoginRequest { username, password })?
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if rsp.status() == 404 {
|
||||||
|
anyhow::bail!("Account not foud")
|
||||||
|
} else if !rsp.ok() {
|
||||||
|
anyhow::bail!("Request failed: {rsp:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
let rsp: LoginResponse = rsp.json().await?;
|
||||||
|
|
||||||
|
LocalStorage::set("token", rsp.token)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
fn Login() -> Html {
|
||||||
|
let error = use_state(|| None);
|
||||||
|
|
||||||
|
let navigator = use_navigator().unwrap();
|
||||||
|
let err = error.clone();
|
||||||
|
let onsubmit = Callback::from(move |e: SubmitEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
|
||||||
|
let document = gloo_utils::document();
|
||||||
|
|
||||||
|
let username: HtmlInputElement = document
|
||||||
|
.get_element_by_id("floatingUser")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into()
|
||||||
|
.expect("floatingUser is not an input element");
|
||||||
|
let username = username.value();
|
||||||
|
|
||||||
|
let password: HtmlInputElement = document
|
||||||
|
.get_element_by_id("floatingPass")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into()
|
||||||
|
.expect("floatingUser is not an input element");
|
||||||
|
let password = password.value();
|
||||||
|
|
||||||
|
let err = err.clone();
|
||||||
|
let navigator = navigator.clone();
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
match do_login(username, password).await {
|
||||||
|
Ok(_) => {
|
||||||
|
navigator.push(&Route::Index);
|
||||||
|
err.set(None);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
err.set(Some(format!("Could not log in: {e:?}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {<>
|
||||||
|
<link href="/login.css" rel="stylesheet" />
|
||||||
|
<form class={classes!("form-signin", "w-100", "m-auto", "text-center")} {onsubmit}>
|
||||||
|
<h1 class={classes!("h3", "mb-3")}>{"Please log in"}</h1>
|
||||||
|
if let Some(err) = &*error {
|
||||||
|
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||||
|
{err}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class={classes!("form-floating")}>
|
||||||
|
<input id="floatingUser" class={classes!("form-control")} placeholder="Username" />
|
||||||
|
<label for="floatingUser">{"Username"}</label>
|
||||||
|
</div>
|
||||||
|
<div class={classes!("form-floating")}>
|
||||||
|
<input
|
||||||
|
id="floatingPass"
|
||||||
|
class={classes!("form-control")}
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
<label for="floatingPass">{"Password"}</label>
|
||||||
|
</div>
|
||||||
|
<button class={classes!("w-100", "btn", "btn-lg", "btn-primary")} type="submit">
|
||||||
|
{"Login"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</>}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
console_log::init_with_level(Level::Debug).unwrap();
|
console_log::init_with_level(Level::Debug).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
33
app/static/login.css
Normal file
33
app/static/login.css
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
main {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-floating:focus-within {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#floatingUser {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#floatingPass {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue