use crate::{ components::router::{AppRoute, NavButton}, infra::api::HostService, }; use anyhow::{anyhow, Result}; use graphql_client::GraphQLQuery; use yew::{ prelude::*, services::{fetch::FetchTask, ConsoleService}, }; #[derive(GraphQLQuery)] #[graphql( schema_path = "../schema.graphql", query_path = "queries/get_user_details.graphql", response_derives = "Debug", custom_scalars_module = "crate::infra::graphql" )] 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::infra::graphql" )] pub struct UpdateUser; pub struct UserDetails { link: ComponentLink, username: String, 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)] 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::( get_user_details::Variables { id: self.username.clone(), }, self.link.callback(Msg::UserDetailsResponse), "Error trying to fetch user details", ) .map_err(|e| { ConsoleService::log(&e.to_string()); e }) .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 { id, display_name, first_name, last_name, email, creation_date, groups, } = self.user.take().unwrap(); let new_user = self.update_request.take().unwrap(); self.user = Some(User { id, email: new_user.email.unwrap_or(email), display_name: new_user.displayName.unwrap_or(display_name), first_name: new_user.firstName.unwrap_or(first_name), last_name: new_user.lastName.unwrap_or(last_name), creation_date, groups, }); } }; } } 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 { type Message = Msg; // The username. type Properties = Props; fn create(props: Self::Properties, link: ComponentLink) -> Self { 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 { self.error = None; match self.handle_msg(msg) { Err(e) => { ConsoleService::error(&e.to_string()); self.error = Some(e); true } Ok(b) => b, } } fn change(&mut self, _: Self::Properties) -> ShouldRender { false } fn view(&self) -> Html { type Group = get_user_details::GetUserDetailsUserGroups; let make_group_row = |group: &Group| { html! { {&group.display_name} } }; 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}
{"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! {} }}
{"Group memberships"} {u.groups.iter().map(make_group_row).collect::>()}
{"Group"}
{"Change password"}
} } } } }