diff --git a/Cargo.lock b/Cargo.lock
index 0b35303..49b5506 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -68,9 +68,17 @@ dependencies = [
name = "app"
version = "0.1.0"
dependencies = [
+ "anyhow",
"api",
"console_log",
+ "gloo-net",
+ "gloo-storage",
+ "gloo-utils",
"log",
+ "serde_json",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
"yew",
"yew-router",
]
diff --git a/app/Cargo.toml b/app/Cargo.toml
index 45af8c2..306e2a8 100644
--- a/app/Cargo.toml
+++ b/app/Cargo.toml
@@ -6,8 +6,16 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+anyhow = "1.0.71"
api = { version = "0.1.0", path = "../api" }
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"
+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-router = "0.17.0"
diff --git a/app/Trunk.toml b/app/Trunk.toml
new file mode 100644
index 0000000..f966ead
--- /dev/null
+++ b/app/Trunk.toml
@@ -0,0 +1,3 @@
+[[hooks]]
+stage = "build"
+command = "./dl_bootstrap.sh"
diff --git a/app/dl_bootstrap.sh b/app/dl_bootstrap.sh
new file mode 100755
index 0000000..f242723
--- /dev/null
+++ b/app/dl_bootstrap.sh
@@ -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
diff --git a/app/index.html b/app/index.html
index 995c87d..a5b92f3 100644
--- a/app/index.html
+++ b/app/index.html
@@ -1,8 +1,10 @@
-
+
+
+
diff --git a/app/src/main.rs b/app/src/main.rs
index 4761443..b3f159a 100644
--- a/app/src/main.rs
+++ b/app/src/main.rs
@@ -1,4 +1,8 @@
+use api::{LoginRequest, LoginResponse};
+use gloo_storage::{LocalStorage, Storage, errors::StorageError};
use log::Level;
+use wasm_bindgen::JsCast;
+use web_sys::HtmlInputElement;
use yew::prelude::*;
use yew_router::prelude::*;
@@ -30,7 +34,7 @@ fn switch(route: Route) -> Html {
},
Route::Login => html! {
- "Login"
+
},
Route::NotFound => html! {
"Page not found"
@@ -40,7 +44,11 @@ fn switch(route: Route) -> Html {
#[function_component]
fn Index() -> Html {
- let token = use_state(|| None::);
+ let token = use_state(|| match LocalStorage::get::("token") {
+ Ok(v) => Some(v),
+ Err(StorageError::KeyNotFound(_)) => None,
+ Err(e) => unreachable!("Could not get token: {e:?}"),
+ });
match &*token {
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! {<>
+
+
+ >}
+}
+
fn main() {
console_log::init_with_level(Level::Debug).unwrap();
diff --git a/app/static/login.css b/app/static/login.css
new file mode 100644
index 0000000..0ea4ed4
--- /dev/null
+++ b/app/static/login.css
@@ -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;
+}