error component wip

This commit is contained in:
2023-06-12 20:30:43 +02:00
parent d786e6fd20
commit 70d99b0c6b
8 changed files with 170 additions and 17 deletions

View File

@@ -1,4 +1,6 @@
use super::error::error_provider::ErrorContext;
use crate::services::rest::RestService; use crate::services::rest::RestService;
use crate::services::rest::RestServiceError;
use common::CreateMilestone; use common::CreateMilestone;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
@@ -13,6 +15,7 @@ use yew_router::scope_ext::RouterScopeExt;
#[derive(Debug)] #[derive(Debug)]
pub enum Msg { pub enum Msg {
Submit, Submit,
SubmitResult(Result<(), RestServiceError>),
UpdateInput(String), UpdateInput(String),
} }
@@ -41,7 +44,7 @@ impl Component for CreateMilestoneComponent {
} }
} }
fn update(&mut self, _ctx: &yew::Context<Self>, msg: Self::Message) -> bool { fn update(&mut self, ctx: &yew::Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::Submit => { Msg::Submit => {
log::info!("Creating achievement"); log::info!("Creating achievement");
@@ -49,16 +52,32 @@ impl Component for CreateMilestoneComponent {
let goal = goal as usize; let goal = goal as usize;
let payload = CreateMilestone { goal }; let payload = CreateMilestone { goal };
let link = ctx.link().clone();
spawn_local(async move { spawn_local(async move {
match RestService::create_milestone(payload).await { let res = RestService::create_milestone(payload).await;
Ok(_response) => {} link.send_message(Msg::SubmitResult(res));
Err(_err) => {}
}
}); });
self.submitted = true; self.submitted = true;
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) => { Msg::UpdateInput(value) => {
self.input_value = value; self.input_value = value;
true true

View File

@@ -0,0 +1,68 @@
use super::error_provider::ErrorContext;
use yew::prelude::*;
pub enum Msg {
ErrorContextUpdated(ErrorContext),
HideError,
}
pub struct ErrorComponent {
error: Option<String>,
_context_listener: ContextHandle<ErrorContext>,
}
impl Component for ErrorComponent {
type Message = Msg;
type Properties = ();
fn create(ctx: &Context<Self>) -> 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<Self>, 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<Self>) -> Html {
let onclick_hide = ctx.link().callback(|_| Msg::HideError);
html! {
<div>
{ self.view_error(onclick_hide) }
</div>
}
}
}
impl ErrorComponent {
fn view_error(&self, onclick_hide: Callback<MouseEvent>) -> Html {
match &self.error {
Some(error_msg) => {
html! {
<div class="error-message">
{ error_msg }
<button onclick={onclick_hide}>
{ "Hide" }
</button>
</div>
}
}
None => html! {},
}
}
}

View File

@@ -0,0 +1,34 @@
use std::rc::Rc;
use yew::prelude::*;
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct Error {
pub value: Option<String>,
}
impl Reducible for Error {
type Action = String;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
Error { value: Some(action) }.into()
}
}
pub type ErrorContext = UseReducerHandle<Error>;
#[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! {
<ContextProvider<ErrorContext> context={err}>
{props.children.clone()}
</ContextProvider<ErrorContext>>
}
}

View File

@@ -0,0 +1,2 @@
pub mod error_component;
pub mod error_provider;

View File

@@ -1,4 +1,8 @@
use crate::services::confirm::ConfirmService;
use crate::services::rest::RestService;
use common::DeleteMilestone;
use common::Milestone; use common::Milestone;
use wasm_bindgen_futures::spawn_local;
use yew::prelude::*; use yew::prelude::*;
#[derive(Properties, Clone, PartialEq)] #[derive(Properties, Clone, PartialEq)]
@@ -9,6 +13,8 @@ pub struct Props {
#[function_component(MilestoneComponent)] #[function_component(MilestoneComponent)]
pub fn milestone_component(props: &Props) -> Html { 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 unfilled = props.milestone.goal - props.completed_achievements.min(props.milestone.goal);
let filled = 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) .take(filled)
.collect::<Html>(); .collect::<Html>();
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! { html! {
<div class="row"> <div class="row flex">
<p style="text-align: center" class="u-full-width"> <p style="text-align: center" class="flex-grow">
{format!("{} / {}", props.completed_achievements, props.milestone.goal)} {format!("{} / {}", props.completed_achievements, props.milestone.goal)}
<br /> <br />
{filled_stars} {filled_stars}
{unfilled_stars} {unfilled_stars}
</p> </p>
<div class="flex-intrinsic-size">
<button onclick={onclick_delete} class="button color-danger"><i class="fas fa-trash"/></button>
</div>
</div> </div>
} }
} }

View File

@@ -2,5 +2,6 @@ pub mod achievement;
pub mod admin; pub mod admin;
pub mod create_achievement; pub mod create_achievement;
pub mod create_milestone; pub mod create_milestone;
pub mod error;
pub mod milestone; pub mod milestone;
pub mod root; pub mod root;

View File

@@ -1,3 +1,5 @@
use crate::components::error::error_component::ErrorComponent;
use crate::components::error::error_provider::ErrorProvider;
use components::admin::Admin; use components::admin::Admin;
use components::create_achievement::CreateAchievementComponent; use components::create_achievement::CreateAchievementComponent;
use components::create_milestone::CreateMilestoneComponent; use components::create_milestone::CreateMilestoneComponent;
@@ -49,11 +51,14 @@ pub fn App() -> Html {
html! { html! {
<ContextProvider<AppState> context={(*ctx).clone()}> <ContextProvider<AppState> context={(*ctx).clone()}>
<BrowserRouter> <ErrorProvider>
<div class="container" style="margin-top: 5%; margin-bottom: 25%"> <div class="container" style="margin-top: 5%; margin-bottom: 25%">
<ErrorComponent />
<BrowserRouter>
<Switch<Route> render={switch}/> <Switch<Route> render={switch}/>
</div>
</BrowserRouter> </BrowserRouter>
</div>
</ErrorProvider>
</ContextProvider<AppState>> </ContextProvider<AppState>>
} }
} }

View File

@@ -29,4 +29,4 @@ subscribe-ws:
websocat ws://127.0.0.1:4000/ws websocat ws://127.0.0.1:4000/ws
trunk-serve: trunk-serve:
trunk serve crates/frontend/index.html trunk serve --address=0.0.0.0 crates/frontend/index.html