regalade/app/src/main.rs

264 lines
7.1 KiB
Rust
Raw Normal View History

2023-05-20 16:53:07 +02:00
use api::{LoginRequest, LoginResponse};
use gloo_storage::{errors::StorageError, LocalStorage, Storage};
2023-05-19 11:23:44 +02:00
use log::Level;
2023-05-20 16:53:07 +02:00
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
2023-05-19 11:23:44 +02:00
use yew::prelude::*;
use yew_router::prelude::*;
2023-05-28 19:48:58 +02:00
use crate::sidebar::RegaladeSidebar;
mod sidebar;
const API_ROUTE: &str = match option_env!("REGALADE_API_SERVER_BASE") {
None => "http://localhost:8085",
Some(v) => v,
};
macro_rules! api {
($($arg:tt)*) => {
&format!("{API_ROUTE}/api/{}", format_args!($($arg)*))
};
}
2023-05-19 11:23:44 +02:00
#[derive(Routable, Debug, Clone, Copy, PartialEq, Eq)]
enum Route {
#[at("/")]
Index,
#[at("/login")]
Login,
#[at("/ingredients")]
Ingredients,
#[at("/household_select")]
HouseholdSelect,
2023-05-19 11:23:44 +02:00
#[at("/404")]
#[not_found]
NotFound,
}
#[function_component]
fn App() -> Html {
html! {
<BrowserRouter>
<Switch<Route> render={switch} />
2023-05-19 11:23:44 +02:00
</BrowserRouter>
}
}
#[derive(Debug, Clone)]
struct RegaladeGlobalState {
token: AttrValue,
household: AttrValue,
}
impl RegaladeGlobalState {
pub fn get_or_navigate(navigator: Navigator) -> Option<Self> {
let token = match LocalStorage::get::<String>("token") {
Ok(v) => v,
Err(StorageError::KeyNotFound(_)) => {
navigator.push(&Route::Login);
return None;
}
Err(e) => unreachable!("Could not get token: {e:?}"),
};
let household = match LocalStorage::get::<String>("household") {
Ok(v) => v,
Err(StorageError::KeyNotFound(_)) => {
navigator.push(&Route::HouseholdSelect);
return None;
}
Err(e) => unreachable!("Could not get household: {e:?}"),
};
Some(Self {
token: token.into(),
household: household.into(),
})
}
pub fn get() -> Self {
let token = match LocalStorage::get::<String>("token") {
Ok(v) => v,
Err(e) => unreachable!("Could not get token: {e:?}"),
};
let household = match LocalStorage::get::<String>("household") {
Ok(v) => v,
Err(e) => unreachable!("Could not get household: {e:?}"),
};
Self {
token: token.into(),
household: household.into(),
}
}
}
#[derive(Debug, PartialEq, Properties)]
struct GlobalStateRedirectorProps {
children: Children,
route: Route,
}
#[function_component]
fn GlobalStateRedirector(props: &GlobalStateRedirectorProps) -> Html {
let navigator = use_navigator().unwrap();
let state = use_state(|| RegaladeGlobalState::get_or_navigate(navigator));
match &*state {
Some(state) => {
html! {
<RegaladeSidebar current={props.route} household={state.household.clone()}>
{ for props.children.iter() }
</RegaladeSidebar>
}
}
None => html! {},
2023-05-19 11:23:44 +02:00
}
}
#[function_component]
fn HouseholdSelection() -> Html {
2023-05-20 16:53:07 +02:00
let token = use_state(|| match LocalStorage::get::<String>("token") {
Ok(v) => Some(v),
Err(StorageError::KeyNotFound(_)) => None,
Err(e) => unreachable!("Could not get household: {e:?}"),
2023-05-20 16:53:07 +02:00
});
match &*token {
None => html! {
<Redirect<Route> to={Route::Login} />
},
Some(_) => html! {
{"Household Selection"}
},
}
}
fn switch(route: Route) -> Html {
match route {
Route::Index => html! {
<GlobalStateRedirector {route}>
{"Index"}
</GlobalStateRedirector>
},
Route::Login => html! {
<Login />
},
Route::Ingredients => html! {
<GlobalStateRedirector {route}>
{"Ingredients"}
</GlobalStateRedirector>
},
Route::HouseholdSelect => html! {
<HouseholdSelection />
},
Route::NotFound => html! {
"Page not found"
},
}
}
2023-05-20 16:53:07 +02:00
async fn do_login(username: String, password: String) -> anyhow::Result<()> {
let rsp = gloo_net::http::Request::post(api!("login"))
2023-05-20 16:53:07 +02:00
.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>
</>}
}
2023-05-19 11:23:44 +02:00
fn main() {
console_log::init_with_level(Level::Debug).unwrap();
yew::Renderer::<App>::with_root(
gloo_utils::document()
.body()
.expect("no body")
.get_elements_by_tag_name("main")
.item(0)
.expect("no main"),
)
.render();
2023-05-19 11:23:44 +02:00
}