From 8bd186fe47eda25b36f945f926ce19093d16fe39 Mon Sep 17 00:00:00 2001 From: cel 🌸 Date: Wed, 11 Jun 2025 01:55:09 +0100 Subject: feat: profile settings panel --- src/views/macaw/settings.rs | 173 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 152 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/views/macaw/settings.rs b/src/views/macaw/settings.rs index 11a3fc3..c4cc99b 100644 --- a/src/views/macaw/settings.rs +++ b/src/views/macaw/settings.rs @@ -4,23 +4,53 @@ use profile_settings::ProfileSettings; use crate::{components::{icon::IconComponent, modal::Modal}, icon::Icon}; mod profile_settings { - use filamento::error::{AvatarPublishError, CommandError}; + use filamento::{error::{AvatarPublishError, CommandError, NickError}, user::User}; use thiserror::Error; use leptos::prelude::*; - use web_sys::{js_sys::Uint8Array, wasm_bindgen::{prelude::Closure, JsCast, UnwrapThrowExt}, Event, FileReader, HtmlInputElement, ProgressEvent}; + use web_sys::{js_sys::Uint8Array, wasm_bindgen::{prelude::Closure, JsCast, UnwrapThrowExt}, Event, FileReader, HtmlInputElement, ProgressEvent, Url}; - use crate::{client::Client, files::Files}; + use crate::{client::Client, files::Files, user::{fetch_avatar, NO_AVATAR}}; #[derive(Debug, Clone, Error)] pub enum ProfileSaveError { #[error("avatar publish: {0}")] Avatar(#[from] CommandError>), + #[error("nick publish: {0}")] + Nick(#[from] CommandError), } #[component] pub fn ProfileSettings() -> impl IntoView { let client: Client = use_context().expect("no client in context"); + let old_profile = LocalResource::new(move || { + let value = client.clone(); + async move { + // TODO: error + let jid = &*value.jid; + let old_profile = value.get_user(jid.clone()).await.unwrap(); + old_profile + } + }); + + view! { + {move || { + if let Some(old_profile) = old_profile.get() { + view! { + + }.into_any() + } else { + view! {}.into_any() + } + }} + } + } + + #[component] + pub fn ProfileForm(old_profile: User) -> impl IntoView { + + let client: Client = use_context().expect("no client in context"); + // TODO: compartmentalise into error component, form component... let (error, set_error) = signal(None::); let error_message = move || { @@ -32,9 +62,24 @@ mod profile_settings { } }) }; + + let (success_message, set_success_message) = signal(None::); + let success_message = move || { + if let Some(message) = success_message.get() { + view! {
{message}
}.into_any() + } else { + view! {}.into_any() + } + }; + let (profile_save_pending, set_profile_save_pending) = signal(false); let profile_upload_data = RwSignal::new(None::>); + let new_nick= RwSignal::new(old_profile.nick.clone().unwrap_or_default().to_string()); + let has_avatar = RwSignal::new(old_profile.avatar.is_some()); + let new_avatar_preview_url = RwSignal::new(None::); + let remove_avatar = RwSignal::new(false); + let from_input = move |ev: Event| { let elem = ev.target().unwrap().unchecked_into::(); @@ -43,6 +88,9 @@ mod profile_settings { let files = elem.files().unwrap_throw(); if let Some(file) = files.get(0) { + let url = Url::create_object_url_with_blob(&file).unwrap_throw(); + + new_avatar_preview_url.set(Some(url)); let reader = FileReader::new().unwrap_throw(); // let name = file.name(); @@ -92,32 +140,115 @@ mod profile_settings { let save_profile = Action::new_local(move |_| { let client = client.clone(); - async move {} + let old_nick = old_profile.nick.clone(); + async move { + set_profile_save_pending.set(true); + + let new_nick = new_nick.get(); + let new_nick = if new_nick.is_empty() { + None + } else { + Some(new_nick) + }; + if new_nick != old_nick { + match client.change_nick(new_nick).await { + Ok(_) => {}, + Err(e) => { + set_error.set(Some(ProfileSaveError::Nick(e))); + set_profile_save_pending.set(false); + return; + }, + } + } + + if let Some(profile_data) = profile_upload_data.get() { + match client.change_avatar(Some(profile_data)).await { + Ok(_) => {}, + Err(e) => { + set_error.set(Some(ProfileSaveError::Avatar(e))); + set_profile_save_pending.set(false); + return; + }, + } + } else if remove_avatar.get() { + match client.change_avatar(None).await { + Ok(_) => {}, + Err(e) => { + set_error.set(Some(ProfileSaveError::Avatar(e))); + set_profile_save_pending.set(false); + return; + }, + } + } + + set_profile_save_pending.set(false); + set_success_message.set(Some("Profile Updated!".to_string())); + } }); - let new_nick= RwSignal::new("".to_string()); + let _old_account_avatar = LocalResource::new(move || { + let avatar = old_profile.avatar.clone(); + async move { + let url = fetch_avatar(avatar.as_deref()).await; + new_avatar_preview_url.set(Some(url)); + } + }); view! {
-
+

Profile Preview

+
+ +
{move || { + let nick = new_nick.get(); + if nick.is_empty() { + old_profile.jid.to_string() + } else { + nick + } + }}
+
+
+ + {success_message} {error_message} -
- +
+

Nick

+ +
+
+

Avatar

+
+ + + {move || { + if has_avatar.get() { + view! { + Remove Avatar + }.into_any() + } else { + view! {}.into_any() + } + }} +
- +
-
-

Profile Preview

-
- -
nick
-
-
} } } @@ -153,10 +284,10 @@ pub fn Settings() -> impl IntoView {
{move || if let Some(page) = show_settings.get() { match page { - SettingsPage::Account => view! {
"account"
}.into_any(), - SettingsPage::Chat => view! {
"chat"
}.into_any(), + SettingsPage::Account => view! {
"Account settings coming soon!"
}.into_any(), + SettingsPage::Chat => view! {
"Chat settings coming soon!"
}.into_any(), SettingsPage::Profile => view! { }.into_any(), - SettingsPage::Privacy => view! {
"privacy"
}.into_any(), + SettingsPage::Privacy => view! {
"Privacy settings coming soon!"
}.into_any(), } } else { view! {}.into_any() -- cgit