diff --git a/app/src/bootstrap.rs b/app/src/bootstrap.rs
index ada8be4..db35e43 100644
--- a/app/src/bootstrap.rs
+++ b/app/src/bootstrap.rs
@@ -58,6 +58,52 @@ pub fn Modal(props: &ModalProps) -> Html {
}
}
+#[derive(Properties, PartialEq)]
+pub struct ConfirmDangerModalProps {
+ pub id: AttrValue,
+ #[prop_or(true)]
+ pub fade: bool,
+ #[prop_or_default]
+ pub centered: bool,
+ pub title: AttrValue,
+ pub on_confirm: Callback<()>,
+ pub children: Children,
+}
+
+#[function_component]
+pub fn ConfirmDangerModal(
+ ConfirmDangerModalProps {
+ id,
+ fade,
+ centered,
+ title,
+ children,
+ on_confirm,
+ }: &ConfirmDangerModalProps,
+) -> Html {
+ let on_confirm = on_confirm.clone();
+ html! {
+
+
+ { for children.iter() }
+
+
+
+
+
+
+ }
+}
+
#[derive(Properties, PartialEq)]
pub struct TitledModalProps {
pub id: AttrValue,
diff --git a/app/src/main.rs b/app/src/main.rs
index 9b1afaa..4706ad4 100644
--- a/app/src/main.rs
+++ b/app/src/main.rs
@@ -25,10 +25,12 @@ const API_ROUTE: &str = match option_env!("REGALADE_API_SERVER_BASE") {
Some(v) => v,
};
+#[macro_export]
macro_rules! api {
- ($($arg:tt)*) => {
+ ($($arg:tt)*) => {{
+ use $crate::API_ROUTE;
&format!("{API_ROUTE}/api/{}", format_args!($($arg)*))
- };
+ }};
}
#[derive(Routable, Debug, Clone, Copy, PartialEq, Eq)]
diff --git a/app/src/sidebar.rs b/app/src/sidebar.rs
index 6ba9b87..bcc0be3 100644
--- a/app/src/sidebar.rs
+++ b/app/src/sidebar.rs
@@ -1,7 +1,10 @@
-use crate::{RegaladeGlobalState, Route};
+use gloo_storage::{LocalStorage, Storage};
+use uuid::Uuid;
use yew::prelude::*;
use yew_router::prelude::*;
+use crate::{api, bootstrap::ConfirmDangerModal, RegaladeGlobalState, Route};
+
#[derive(PartialEq)]
struct MenuEntry {
icon: &'static str,
@@ -16,9 +19,54 @@ struct SidebarProps {
children: Children,
}
+async fn do_leave(token: String, household: Uuid) -> anyhow::Result<()> {
+ let rsp = gloo_net::http::Request::delete(api!("household/{household}"))
+ .header("Authorization", &format!("Bearer {token}"))
+ .send()
+ .await?;
+
+ if !rsp.ok() {
+ let body = rsp.body();
+ match body {
+ None => anyhow::bail!("Could not leave: {rsp:?}"),
+ Some(s) => anyhow::bail!("Could not leave: {}", s.to_string()),
+ }
+ }
+
+ LocalStorage::delete("household");
+
+ Ok(())
+}
+
#[function_component]
fn Sidebar(props: &SidebarProps) -> Html {
let global_state = use_state(RegaladeGlobalState::get);
+ let navigator = use_navigator().unwrap();
+
+ let token = global_state.token.token.clone();
+ let household = global_state.household.id;
+ let nav = navigator.clone();
+ let leave_household = Callback::from(move |()| {
+ let token = token.clone();
+ let nav = nav.clone();
+ wasm_bindgen_futures::spawn_local(async move {
+ match do_leave(token, household).await {
+ Err(e) => {
+ log::error!("Could not leave household: {e:?}");
+ }
+ Ok(_) => {
+ nav.push(&Route::HouseholdSelect);
+ }
+ }
+ })
+ });
+
+ let logout = Callback::from(move |_| {
+ LocalStorage::delete("token");
+ LocalStorage::delete("household");
+
+ navigator.push(&Route::HouseholdSelect);
+ });
html! {
@@ -105,7 +153,35 @@ fn Sidebar(props: &SidebarProps) -> Html {
{format!("{} ({})", global_state.household.name, global_state.token.name)}
+
+ {format!("Are you sure you want to leave the household '{}' ?", global_state.household.name)}
+