diff --git a/app/queries/update_user.graphql b/app/queries/update_user.graphql new file mode 100644 index 0000000..63272da --- /dev/null +++ b/app/queries/update_user.graphql @@ -0,0 +1,5 @@ +mutation UpdateUser($user: UpdateUserInput!) { + updateUser(user: $user) { + ok + } +} diff --git a/app/src/user_details.rs b/app/src/user_details.rs index eb93e17..39923a5 100644 --- a/app/src/user_details.rs +++ b/app/src/user_details.rs @@ -15,16 +15,36 @@ pub struct GetUserDetails; type User = get_user_details::GetUserDetailsUser; +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "../schema.graphql", + query_path = "queries/update_user.graphql", + response_derives = "Debug", + variables_derives = "Clone", + custom_scalars_module = "crate::graphql" +)] +pub struct UpdateUser; + pub struct UserDetails { link: ComponentLink, username: String, - user: Option>, + user: Option, + // Needed for the form. + node_ref: NodeRef, + // Error message displayed to the user. + error: Option, + // The request, while we're waiting for the server to reply. + update_request: Option, + // True iff we just finished updating the user, to display a successful message. + update_successful: bool, // Used to keep the request alive long enough. _task: Option, } pub enum Msg { UserDetailsResponse(Result), + SubmitForm, + UpdateFinished(Result), } #[derive(yew::Properties, Clone, PartialEq)] @@ -32,6 +52,11 @@ pub struct Props { pub username: String, } +#[allow(clippy::ptr_arg)] +fn not_empty(s: &String) -> bool { + !s.is_empty() +} + impl UserDetails { fn get_user_details(&mut self) { self._task = HostService::graphql_query::( @@ -47,6 +72,99 @@ impl UserDetails { }) .ok(); } + fn handle_msg(&mut self, msg: ::Message) -> Result { + self.update_successful = false; + match msg { + Msg::UserDetailsResponse(Ok(user)) => { + self.user = Some(user.user); + } + Msg::UserDetailsResponse(Err(e)) => { + self.error = Some(anyhow!("Error getting user details: {}", e)); + self.user = None; + } + Msg::SubmitForm => { + let base_user = self.user.as_ref().unwrap(); + let mut user_input = update_user::UpdateUserInput { + id: self.username.clone(), + email: None, + displayName: None, + firstName: None, + lastName: None, + }; + let mut should_send_form = false; + let email = get_element("email") + .filter(not_empty) + .ok_or_else(|| anyhow!("Missing email"))?; + if base_user.email != email { + should_send_form = true; + user_input.email = Some(email); + } + if base_user.display_name != get_element_or_empty("display_name") { + should_send_form = true; + user_input.displayName = Some(get_element_or_empty("display_name")); + } + if base_user.first_name != get_element_or_empty("first_name") { + should_send_form = true; + user_input.firstName = Some(get_element_or_empty("first_name")); + } + if base_user.last_name != get_element_or_empty("last_name") { + should_send_form = true; + user_input.lastName = Some(get_element_or_empty("last_name")); + } + if !should_send_form { + return Ok(false); + } + self.update_request = Some(user_input.clone()); + let req = update_user::Variables { user: user_input }; + self._task = Some(HostService::graphql_query::( + req, + self.link.callback(Msg::UpdateFinished), + "Error trying to update user", + )?); + return Ok(false); + } + Msg::UpdateFinished(r) => { + match r { + Err(e) => return Err(e), + Ok(_) => { + ConsoleService::log("Successfully updated user"); + self.update_successful = true; + let user = self.user.as_ref().unwrap(); + let new_user = self.update_request.take().unwrap(); + self.user = Some(User { + id: user.id.clone(), + email: new_user.email.unwrap_or_else(|| user.email.clone()), + display_name: new_user + .displayName + .unwrap_or_else(|| user.display_name.clone()), + first_name: new_user + .firstName + .unwrap_or_else(|| user.first_name.clone()), + last_name: new_user.lastName.unwrap_or_else(|| user.last_name.clone()), + creation_date: user.creation_date, + }); + } + }; + } + } + Ok(true) + } +} + +fn get_element(name: &str) -> Option { + use wasm_bindgen::JsCast; + Some( + web_sys::window()? + .document()? + .get_element_by_id(name)? + .dyn_into::() + .ok()? + .value(), + ) +} + +fn get_element_or_empty(name: &str) -> String { + get_element(name).unwrap_or_default() } impl Component for UserDetails { @@ -58,23 +176,26 @@ impl Component for UserDetails { let mut table = UserDetails { link, username: props.username, + node_ref: NodeRef::default(), _task: None, user: None, + error: None, + update_request: None, + update_successful: false, }; table.get_user_details(); table } fn update(&mut self, msg: Self::Message) -> ShouldRender { - match msg { - Msg::UserDetailsResponse(Ok(user)) => { - self.user = Some(Ok(user.user)); - true - } - Msg::UserDetailsResponse(Err(e)) => { - self.user = Some(Err(anyhow!("Error getting user details: {}", e))); + self.error = None; + match self.handle_msg(msg) { + Err(e) => { + ConsoleService::error(&e.to_string()); + self.error = Some(e); true } + Ok(b) => b, } } @@ -83,19 +204,51 @@ impl Component for UserDetails { } fn view(&self) -> Html { - match &self.user { - None => html! {{"Loading..."}}, - Some(Err(e)) => html! {
{"Error: "}{e.to_string()}
}, - Some(Ok(u)) => { + match (&self.user, &self.error) { + (None, None) => html! {{"Loading..."}}, + (None, Some(e)) => html! {
{"Error: "}{e.to_string()}
}, + (Some(u), error) => { html! { -
-
{"User ID: "} {&u.id}
-
{"Email: "}{&u.email}
-
{"Display name: "}{&u.display_name}
-
{"First name: "}{&u.first_name}
-
{"Last name: "}{&u.last_name}
-
{"Creation date: "}{&u.creation_date.with_timezone(&chrono::Local)}
-
+
+
+ {"User ID: "} + {&u.id} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ {"Creation date: "} + {&u.creation_date.with_timezone(&chrono::Local)} +
+
+ +
+ { if self.update_successful { + html! { + {"Update successful!"} + } + } else if let Some(e) = error { + html! { +
+ {"Error: "}{e.to_string()} +
+ } + } else { html! {} }} +
} } }