app: Allow to add users at household creation
This commit is contained in:
parent
b9666726d4
commit
b40ddc307d
1 changed files with 166 additions and 33 deletions
199
app/src/main.rs
199
app/src/main.rs
|
|
@ -9,7 +9,8 @@ use yew::{prelude::*, suspense::use_future};
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
use api::{
|
use api::{
|
||||||
CreateHouseholdRequest, CreateHouseholdResponse, Household, LoginRequest, LoginResponse,
|
AddToHouseholdRequest, CreateHouseholdRequest, CreateHouseholdResponse, Household,
|
||||||
|
LoginRequest, LoginResponse, UserInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -180,8 +181,6 @@ fn HouseholdListSelect() -> HtmlResult {
|
||||||
let name = info.name.clone();
|
let name = info.name.clone();
|
||||||
let nav = navigator.clone();
|
let nav = navigator.clone();
|
||||||
let onclick = Callback::from(move |_| {
|
let onclick = Callback::from(move |_| {
|
||||||
log::info!("Clicked {id}");
|
|
||||||
|
|
||||||
if let Err(e) = LocalStorage::set(
|
if let Err(e) = LocalStorage::set(
|
||||||
"household",
|
"household",
|
||||||
HouseholdInfo {
|
HouseholdInfo {
|
||||||
|
|
@ -222,21 +221,61 @@ fn HouseholdListSelect() -> HtmlResult {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
struct CreateHouseholdProps {
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_resolve_user(token: String, username: String) -> anyhow::Result<Option<Uuid>> {
|
||||||
|
let rsp = gloo_net::http::Request::get(api!("search/user/{username}"))
|
||||||
|
.header("Authorization", &format!("Bearer {token}"))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if rsp.status() == 404 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rsp.ok() {
|
||||||
|
anyhow::bail!("Request failed: {rsp:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
let rsp: UserInfo = rsp.json().await?;
|
||||||
|
|
||||||
|
Ok(Some(rsp.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn do_add_user_to_household(
|
||||||
|
token: String,
|
||||||
|
household: Uuid,
|
||||||
|
user: Uuid,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let rsp = gloo_net::http::Request::put(api!("household/{household}"))
|
||||||
|
.json(&AddToHouseholdRequest { user })?
|
||||||
|
.header("Authorization", &format!("Bearer {token}"))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !rsp.ok() {
|
||||||
|
anyhow::bail!("Request failed: {rsp:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
fn HouseholdSelection() -> Html {
|
fn CreateHousehold(props: &CreateHouseholdProps) -> Html {
|
||||||
let token = use_state(|| match LocalStorage::get::<LoginInfo>("token") {
|
|
||||||
Ok(v) => Some(v),
|
|
||||||
Err(StorageError::KeyNotFound(_)) => None,
|
|
||||||
Err(e) => unreachable!("Could not get household: {e:?}"),
|
|
||||||
});
|
|
||||||
let error = use_state(|| None::<String>);
|
let error = use_state(|| None::<String>);
|
||||||
let navigator = use_navigator().unwrap();
|
let navigator = use_navigator().unwrap();
|
||||||
|
|
||||||
|
let members = use_state(Vec::<(Uuid, String)>::new);
|
||||||
|
|
||||||
let err = error.clone();
|
let err = error.clone();
|
||||||
let tok = token.clone();
|
let tok = props.token.clone();
|
||||||
|
let mem = members.clone();
|
||||||
let on_submit = Callback::from(move |()| {
|
let on_submit = Callback::from(move |()| {
|
||||||
let document = gloo_utils::document();
|
let document = gloo_utils::document();
|
||||||
let token = tok.as_ref().unwrap().to_owned();
|
let token = tok.clone();
|
||||||
|
|
||||||
let name: HtmlInputElement = document
|
let name: HtmlInputElement = document
|
||||||
.get_element_by_id("newHsName")
|
.get_element_by_id("newHsName")
|
||||||
|
|
@ -245,13 +284,23 @@ fn HouseholdSelection() -> Html {
|
||||||
.expect("newHsName is not an input element");
|
.expect("newHsName is not an input element");
|
||||||
let name = name.value();
|
let name = name.value();
|
||||||
|
|
||||||
|
let members = mem.clone();
|
||||||
let err = err.clone();
|
let err = err.clone();
|
||||||
let navigator = navigator.clone();
|
let navigator = navigator.clone();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
match do_new_household(token.token, name.clone()).await {
|
match do_new_household(token.clone(), name.clone()).await {
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
let household_info = HouseholdInfo { name, id };
|
let household_info = HouseholdInfo { name, id };
|
||||||
|
|
||||||
|
for (uid, user) in &*members {
|
||||||
|
if let Err(e) = do_add_user_to_household(token.clone(), id, *uid).await {
|
||||||
|
err.set(Some(format!(
|
||||||
|
"Could not add user {user} (but household was created): {e:?}"
|
||||||
|
)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = LocalStorage::set("household", household_info) {
|
if let Err(e) = LocalStorage::set("household", household_info) {
|
||||||
log::error!("Could not switch to new household: {e:?}")
|
log::error!("Could not switch to new household: {e:?}")
|
||||||
}
|
}
|
||||||
|
|
@ -261,6 +310,7 @@ fn HouseholdSelection() -> Html {
|
||||||
|
|
||||||
navigator.push(&Route::Index);
|
navigator.push(&Route::Index);
|
||||||
err.set(None);
|
err.set(None);
|
||||||
|
members.set(Vec::new());
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
err.set(Some(format!("Could not create: {e:?}")));
|
err.set(Some(format!("Could not create: {e:?}")));
|
||||||
|
|
@ -269,6 +319,109 @@ fn HouseholdSelection() -> Html {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let err = error.clone();
|
||||||
|
let tok = props.token.clone();
|
||||||
|
let mem = members.clone();
|
||||||
|
let add_member = Callback::from(move |_| {
|
||||||
|
let document = gloo_utils::document();
|
||||||
|
|
||||||
|
let username: HtmlInputElement = document
|
||||||
|
.get_element_by_id("newHsAddMember")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into()
|
||||||
|
.expect("newHsAddMember is not an input element");
|
||||||
|
let username = username.value();
|
||||||
|
|
||||||
|
let tok = tok.clone();
|
||||||
|
let err = err.clone();
|
||||||
|
let members = mem.clone();
|
||||||
|
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
match do_resolve_user(tok.clone(), username.clone()).await {
|
||||||
|
Err(e) => {
|
||||||
|
err.set(Some(format!("Error adding a member: {e:?}")));
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
err.set(Some(format!("User '{username}' does not exist")));
|
||||||
|
}
|
||||||
|
Ok(Some(id)) => {
|
||||||
|
let mut m = (*members).clone();
|
||||||
|
m.push((id, username));
|
||||||
|
members.set(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let mem = members.clone();
|
||||||
|
let remove_user = |idx| {
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let mut m = (*mem).clone();
|
||||||
|
m.remove(idx);
|
||||||
|
mem.set(m);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<FormModal
|
||||||
|
id="newHsModal"
|
||||||
|
centered={true}
|
||||||
|
submit_label={"Create"}
|
||||||
|
title="Create a Household"
|
||||||
|
{on_submit}
|
||||||
|
>
|
||||||
|
if let Some(err) = &*error {
|
||||||
|
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||||
|
{err}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="form-floating">
|
||||||
|
<input
|
||||||
|
id="newHsName"
|
||||||
|
class={classes!("form-control")}
|
||||||
|
placeholder="Household name"
|
||||||
|
/>
|
||||||
|
<label for="newHsName">{"Household name"}</label>
|
||||||
|
</div>
|
||||||
|
<h2 class={classes!("fs-5", "m-2")}>{"Additional Members"}</h2>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
{
|
||||||
|
for members.iter().enumerate().map(move |(idx, (_, name))| html!{
|
||||||
|
<li class="list-group-item">
|
||||||
|
{name}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={classes!("btn", "btn-danger", "ms-2")}
|
||||||
|
onclick={remove_user.clone()(idx)}
|
||||||
|
>
|
||||||
|
{"Remove"}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<div class="d-flex flex-row">
|
||||||
|
<input
|
||||||
|
id="newHsAddMember"
|
||||||
|
class={classes!("form-control", "me-2")}
|
||||||
|
placeholder="Additional member"
|
||||||
|
/>
|
||||||
|
<button type="button" class={classes!("btn", "btn-primary")} onclick={add_member}>
|
||||||
|
{"Add"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</FormModal>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
fn HouseholdSelection() -> Html {
|
||||||
|
let token = use_state(|| match LocalStorage::get::<LoginInfo>("token") {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(StorageError::KeyNotFound(_)) => None,
|
||||||
|
Err(e) => unreachable!("Could not get household: {e:?}"),
|
||||||
|
});
|
||||||
|
|
||||||
let fallback = html! { {"Loading..."} };
|
let fallback = html! { {"Loading..."} };
|
||||||
|
|
||||||
match &*token {
|
match &*token {
|
||||||
|
|
@ -290,27 +443,7 @@ fn HouseholdSelection() -> Html {
|
||||||
>
|
>
|
||||||
{"New household"}
|
{"New household"}
|
||||||
</ModalToggleButton>
|
</ModalToggleButton>
|
||||||
<FormModal
|
<CreateHousehold token={token.as_ref().unwrap().token.to_owned()} />
|
||||||
id="newHsModal"
|
|
||||||
centered={true}
|
|
||||||
submit_label={"Create"}
|
|
||||||
title="Create a Household"
|
|
||||||
{on_submit}
|
|
||||||
>
|
|
||||||
if let Some(err) = &*error {
|
|
||||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
|
||||||
{err}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="form-floating">
|
|
||||||
<input
|
|
||||||
id="newHsName"
|
|
||||||
class={classes!("form-control")}
|
|
||||||
placeholder="Household name"
|
|
||||||
/>
|
|
||||||
<label for="newHsName">{"Household name"}</label>
|
|
||||||
</div>
|
|
||||||
</FormModal>
|
|
||||||
</div>
|
</div>
|
||||||
</>},
|
</>},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue