From 70d99b0c6bd50361f69fe8260ae6bfb4d376c606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asger=20Juul=20Brunsh=C3=B8j?= Date: Mon, 12 Jun 2023 20:30:43 +0200 Subject: [PATCH] error component wip --- .../src/components/create_milestone.rs | 29 ++++++-- .../src/components/error/error_component.rs | 68 +++++++++++++++++++ .../src/components/error/error_provider.rs | 34 ++++++++++ crates/frontend/src/components/error/mod.rs | 2 + crates/frontend/src/components/milestone.rs | 38 +++++++++-- crates/frontend/src/components/mod.rs | 1 + crates/frontend/src/lib.rs | 13 ++-- justfile | 2 +- 8 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 crates/frontend/src/components/error/error_component.rs create mode 100644 crates/frontend/src/components/error/error_provider.rs create mode 100644 crates/frontend/src/components/error/mod.rs diff --git a/crates/frontend/src/components/create_milestone.rs b/crates/frontend/src/components/create_milestone.rs index 852a97e..09fed0c 100644 --- a/crates/frontend/src/components/create_milestone.rs +++ b/crates/frontend/src/components/create_milestone.rs @@ -1,4 +1,6 @@ +use super::error::error_provider::ErrorContext; use crate::services::rest::RestService; +use crate::services::rest::RestServiceError; use common::CreateMilestone; use wasm_bindgen::JsCast; use wasm_bindgen_futures::spawn_local; @@ -13,6 +15,7 @@ use yew_router::scope_ext::RouterScopeExt; #[derive(Debug)] pub enum Msg { Submit, + SubmitResult(Result<(), RestServiceError>), UpdateInput(String), } @@ -41,7 +44,7 @@ impl Component for CreateMilestoneComponent { } } - fn update(&mut self, _ctx: &yew::Context, msg: Self::Message) -> bool { + fn update(&mut self, ctx: &yew::Context, msg: Self::Message) -> bool { match msg { Msg::Submit => { log::info!("Creating achievement"); @@ -49,16 +52,32 @@ impl Component for CreateMilestoneComponent { let goal = goal as usize; let payload = CreateMilestone { goal }; + let link = ctx.link().clone(); spawn_local(async move { - match RestService::create_milestone(payload).await { - Ok(_response) => {} - Err(_err) => {} - } + let res = RestService::create_milestone(payload).await; + link.send_message(Msg::SubmitResult(res)); }); self.submitted = true; true } + Msg::SubmitResult(result) => { + match result { + Ok(_response) => { + let nav = ctx.link().navigator().unwrap(); + nav.push(&crate::Route::Root); + } + Err(err) => { + let (error_context, _context_listener) = ctx + .link() + .context(Callback::from(|_: ErrorContext| ())) + .expect("No Error Context Provided"); + log::info!("dispatching error"); + error_context.dispatch(err.to_string()); + } + }; + true + } Msg::UpdateInput(value) => { self.input_value = value; true diff --git a/crates/frontend/src/components/error/error_component.rs b/crates/frontend/src/components/error/error_component.rs new file mode 100644 index 0000000..529eb51 --- /dev/null +++ b/crates/frontend/src/components/error/error_component.rs @@ -0,0 +1,68 @@ +use super::error_provider::ErrorContext; +use yew::prelude::*; + +pub enum Msg { + ErrorContextUpdated(ErrorContext), + HideError, +} + +pub struct ErrorComponent { + error: Option, + _context_listener: ContextHandle, +} + +impl Component for ErrorComponent { + type Message = Msg; + type Properties = (); + + fn create(ctx: &Context) -> Self { + let (error_context, context_listener) = ctx + .link() + .context(ctx.link().callback(Msg::ErrorContextUpdated)) + .expect("No Error Context Provided"); + Self { + error: error_context.value.clone(), + _context_listener: context_listener, + } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::ErrorContextUpdated(error_context) => { + self.error = error_context.value.clone(); + true + } + Msg::HideError => { + self.error = None; + true + } + } + } + + fn view(&self, ctx: &Context) -> Html { + let onclick_hide = ctx.link().callback(|_| Msg::HideError); + html! { +
+ { self.view_error(onclick_hide) } +
+ } + } +} + +impl ErrorComponent { + fn view_error(&self, onclick_hide: Callback) -> Html { + match &self.error { + Some(error_msg) => { + html! { +
+ { error_msg } + +
+ } + } + None => html! {}, + } + } +} diff --git a/crates/frontend/src/components/error/error_provider.rs b/crates/frontend/src/components/error/error_provider.rs new file mode 100644 index 0000000..91e5657 --- /dev/null +++ b/crates/frontend/src/components/error/error_provider.rs @@ -0,0 +1,34 @@ +use std::rc::Rc; +use yew::prelude::*; + +#[derive(Default, Debug, PartialEq, Eq, Clone)] +pub struct Error { + pub value: Option, +} + +impl Reducible for Error { + type Action = String; + + fn reduce(self: Rc, action: Self::Action) -> Rc { + Error { value: Some(action) }.into() + } +} + +pub type ErrorContext = UseReducerHandle; + +#[derive(Properties, Debug, PartialEq)] +pub struct ErrorProviderProps { + #[prop_or_default] + pub children: Children, +} + +#[function_component] +pub fn ErrorProvider(props: &ErrorProviderProps) -> Html { + let err = use_reducer(Default::default); + + html! { + context={err}> + {props.children.clone()} + > + } +} diff --git a/crates/frontend/src/components/error/mod.rs b/crates/frontend/src/components/error/mod.rs new file mode 100644 index 0000000..65cdd26 --- /dev/null +++ b/crates/frontend/src/components/error/mod.rs @@ -0,0 +1,2 @@ +pub mod error_component; +pub mod error_provider; diff --git a/crates/frontend/src/components/milestone.rs b/crates/frontend/src/components/milestone.rs index f15670f..90a53c3 100644 --- a/crates/frontend/src/components/milestone.rs +++ b/crates/frontend/src/components/milestone.rs @@ -1,4 +1,8 @@ +use crate::services::confirm::ConfirmService; +use crate::services::rest::RestService; +use common::DeleteMilestone; use common::Milestone; +use wasm_bindgen_futures::spawn_local; use yew::prelude::*; #[derive(Properties, Clone, PartialEq)] @@ -9,6 +13,8 @@ pub struct Props { #[function_component(MilestoneComponent)] pub fn milestone_component(props: &Props) -> Html { + let confirm_service = use_memo(|_| ConfirmService, ()); + let unfilled = props.milestone.goal - props.completed_achievements.min(props.milestone.goal); let filled = props.completed_achievements.min(props.milestone.goal); @@ -24,14 +30,32 @@ pub fn milestone_component(props: &Props) -> Html { .take(filled) .collect::(); + let uuid = props.milestone.uuid; + let onclick_delete = Callback::from(move |_| { + if !confirm_service.confirm("Are you sure you want to delete?") { + return; + } + log::info!("Delete achievement confirmed."); + + spawn_local(async move { + match RestService::delete_milestone(DeleteMilestone { uuid }).await { + Ok(_response) => {} + Err(_err) => {} + } + }); + }); + html! { -
-

- {format!("{} / {}", props.completed_achievements, props.milestone.goal)} -
- {filled_stars} - {unfilled_stars} -

+
+

+ {format!("{} / {}", props.completed_achievements, props.milestone.goal)} +
+ {filled_stars} + {unfilled_stars} +

+
+ +
} } diff --git a/crates/frontend/src/components/mod.rs b/crates/frontend/src/components/mod.rs index 835c031..ee47d61 100644 --- a/crates/frontend/src/components/mod.rs +++ b/crates/frontend/src/components/mod.rs @@ -2,5 +2,6 @@ pub mod achievement; pub mod admin; pub mod create_achievement; pub mod create_milestone; +pub mod error; pub mod milestone; pub mod root; diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs index ea648e1..c11be58 100644 --- a/crates/frontend/src/lib.rs +++ b/crates/frontend/src/lib.rs @@ -1,3 +1,5 @@ +use crate::components::error::error_component::ErrorComponent; +use crate::components::error::error_provider::ErrorProvider; use components::admin::Admin; use components::create_achievement::CreateAchievementComponent; use components::create_milestone::CreateMilestoneComponent; @@ -49,11 +51,14 @@ pub fn App() -> Html { html! { context={(*ctx).clone()}> - -
+ +
+ + render={switch}/> -
- + +
+
> } } diff --git a/justfile b/justfile index a6d7e1c..25e18b0 100644 --- a/justfile +++ b/justfile @@ -29,4 +29,4 @@ subscribe-ws: websocat ws://127.0.0.1:4000/ws trunk-serve: - trunk serve crates/frontend/index.html + trunk serve --address=0.0.0.0 crates/frontend/index.html