synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;
use crate::components::avatar::Avatar;
use crate::state::app_state::{AppState, RightPanelView};

/// Right panel showing detailed information about a specific room member.
#[component]
pub fn MemberInfoPanel(user_id: String) -> Element {
    let mut state = use_context::<Signal<AppState>>();
    let mut display_name = use_signal(|| user_id.clone());
    let mut avatar_url = use_signal(|| Option::<String>::None);
    let mut power_level = use_signal(|| 0i64);
    let mut loaded = use_signal(|| false);

    // Load member profile from SDK
    if !*loaded.read() {
        loaded.set(true);
        let uid = user_id.clone();
        spawn(async move {
            let (client, active_room_id) = {
                let s = state.read();
                (s.client.clone(), s.active_room_id.clone())
            };
            if let (Some(client), Some(room_id)) = (client, active_room_id) {
                if let Some(room) = client.get_room(&room_id) {
                    if let Ok(target_user) = matrix_sdk::ruma::OwnedUserId::try_from(uid.as_str()) {
                        // Get the member from the room
                        match room.get_member(&target_user).await {
                            Ok(Some(member)) => {
                                if let Some(name) = member.display_name() {
                                    display_name.set(name.to_string());
                                }
                                if let Some(url) = member.avatar_url() {
                                    avatar_url.set(Some(url.to_string()));
                                }
                                let pl = member.power_level();
                                let pl_val = match pl {
                                    matrix_sdk::ruma::events::room::power_levels::UserPowerLevel::Int(i) => i64::from(i),
                                    _ => 100,
                                };
                                power_level.set(pl_val);
                            }
                            Ok(None) => {
                                tracing::debug!("Member {uid} not found in room");
                            }
                            Err(e) => {
                                tracing::error!("Failed to get member {uid}: {e}");
                            }
                        }
                    }
                }
            }
        });
    }

    let close_panel = move |_| {
        state.write().right_panel = RightPanelView::Closed;
    };

    let back_to_members = move |_| {
        state.write().right_panel = RightPanelView::MemberList;
    };

    let pl = *power_level.read();
    let role_label = match pl {
        100.. => "Admin",
        50..=99 => "Moderator",
        _ => "Default",
    };

    let user_id_for_dm = user_id.clone();
    let on_start_dm = move |_| {
        let uid = user_id_for_dm.clone();
        spawn(async move {
            let client = { state.read().client.clone() };
            if let Some(client) = client {
                if let Ok(target) = matrix_sdk::ruma::OwnedUserId::try_from(uid.as_str()) {
                    // Check if a DM already exists with this user
                    let mut found_dm: Option<matrix_sdk::ruma::OwnedRoomId> = None;
                    for room in client.rooms() {
                        // is_direct() is async in current SDK
                        if room.is_direct().await.unwrap_or(false) {
                            let targets = room.direct_targets();
                            let target_str = target.to_string();
                            let has_target = targets
                                .iter()
                                .any(|t| t.to_string() == target_str);
                            if has_target {
                                found_dm = Some(room.room_id().to_owned());
                                break;
                            }
                        }
                    }

                    if let Some(rid) = found_dm {
                        state.write().active_room_id = Some(rid);
                        state.write().right_panel = RightPanelView::Closed;
                    } else {
                        // Create a new DM room
                        use matrix_sdk::ruma::api::client::room::create_room::v3::Request as CreateRoomRequest;
                        let mut request = CreateRoomRequest::new();
                        request.is_direct = true;
                        request.invite = vec![target];
                        request.preset = Some(matrix_sdk::ruma::api::client::room::create_room::v3::RoomPreset::TrustedPrivateChat);

                        match client.send(request).await {
                            Ok(response) => {
                                tracing::info!("Created DM room: {}", response.room_id);
                                state.write().active_room_id = Some(response.room_id);
                                state.write().right_panel = RightPanelView::Closed;
                            }
                            Err(e) => {
                                tracing::error!("Failed to create DM: {e}");
                            }
                        }
                    }
                }
            }
        });
    };

    let user_id_for_ignore = user_id.clone();
    let on_toggle_ignore = move |_| {
        let uid = user_id_for_ignore.clone();
        spawn(async move {
            let client = { state.read().client.clone() };
            if let Some(client) = client {
                if let Ok(target) = matrix_sdk::ruma::OwnedUserId::try_from(uid.as_str()) {
                    match client.account().ignore_user(&target).await {
                        Ok(_) => tracing::info!("Ignored user {target}"),
                        Err(e) => tracing::error!("Failed to ignore user: {e}"),
                    }
                }
            }
        });
    };

    // Track ignore state
    let is_ignored = use_signal(|| false);

    // Moderation actions
    let mut mod_error = use_signal(|| Option::<String>::None);

    let user_id_for_kick = user_id.clone();
    let on_kick = move |_| {
        let uid = user_id_for_kick.clone();
        mod_error.set(None);
        spawn(async move {
            let (client, active_room_id) = {
                let s = state.read();
                (s.client.clone(), s.active_room_id.clone())
            };
            if let (Some(client), Some(room_id)) = (client, active_room_id) {
                if let Some(room) = client.get_room(&room_id) {
                    if let Ok(target) = matrix_sdk::ruma::OwnedUserId::try_from(uid.as_str()) {
                        match room.kick_user(&target, Some("Kicked by room admin")).await {
                            Ok(_) => {
                                tracing::info!("Kicked user {target}");
                                state.write().right_panel = RightPanelView::MemberList;
                            }
                            Err(e) => {
                                tracing::error!("Failed to kick user: {e}");
                                mod_error.set(Some(format!("Kick failed: {e}")));
                            }
                        }
                    }
                }
            }
        });
    };

    let user_id_for_ban = user_id.clone();
    let on_ban = move |_| {
        let uid = user_id_for_ban.clone();
        mod_error.set(None);
        spawn(async move {
            let (client, active_room_id) = {
                let s = state.read();
                (s.client.clone(), s.active_room_id.clone())
            };
            if let (Some(client), Some(room_id)) = (client, active_room_id) {
                if let Some(room) = client.get_room(&room_id) {
                    if let Ok(target) = matrix_sdk::ruma::OwnedUserId::try_from(uid.as_str()) {
                        match room.ban_user(&target, Some("Banned by room admin")).await {
                            Ok(_) => {
                                tracing::info!("Banned user {target}");
                                state.write().right_panel = RightPanelView::MemberList;
                            }
                            Err(e) => {
                                tracing::error!("Failed to ban user: {e}");
                                mod_error.set(Some(format!("Ban failed: {e}")));
                            }
                        }
                    }
                }
            }
        });
    };

    let user_id_for_mute = user_id.clone();
    let on_mute = move |_| {
        let uid = user_id_for_mute.clone();
        mod_error.set(None);
        spawn(async move {
            let (client, active_room_id) = {
                let s = state.read();
                (s.client.clone(), s.active_room_id.clone())
            };
            if let (Some(client), Some(room_id)) = (client, active_room_id) {
                if let Some(room) = client.get_room(&room_id) {
                    if let Ok(target) = matrix_sdk::ruma::OwnedUserId::try_from(uid.as_str()) {
                        // Mute by setting power level to -1 (below default send level)
                        // Use update_power_levels which handles the read-modify-write
                        match room.update_power_levels(vec![(&target, (-1).into())]).await {
                            Ok(_) => {
                                tracing::info!("Muted user {target}");
                                power_level.set(-1);
                            }
                            Err(e) => {
                                tracing::error!("Failed to mute user: {e}");
                                mod_error.set(Some(format!("Mute failed: {e}")));
                            }
                        }
                    }
                }
            }
        });
    };

    let dn = display_name.read().clone();
    let av = avatar_url.read().clone();
    let has_mod_error = mod_error.read().is_some();
    let mod_error_text = mod_error.read().clone().unwrap_or_default();

    rsx! {
        div {
            class: "member-info-panel",

            // Header
            header {
                class: "member-info-panel__header",
                button {
                    class: "member-info-panel__back-btn",
                    title: "Back to member list",
                    onclick: back_to_members,
                    ""
                }
                h3 {
                    class: "member-info-panel__title",
                    "Member Info"
                }
                button {
                    class: "member-info-panel__close-btn",
                    title: "Close panel",
                    onclick: close_panel,
                    ""
                }
            }

            // Profile card
            div {
                class: "member-info-panel__profile",
                Avatar {
                    name: dn.clone(),
                    url: av,
                    size: 72,
                }
                h2 {
                    class: "member-info-panel__display-name",
                    "{dn}"
                }
                p {
                    class: "member-info-panel__user-id",
                    "{user_id}"
                }
            }

            // Role / power level section
            div {
                class: "member-info-panel__section",
                h4 { class: "member-info-panel__section-title", "Role in this room" }
                p {
                    class: "member-info-panel__role",
                    "{role_label} (power level: {pl})"
                }
            }

            // Error display
            if has_mod_error {
                div {
                    class: "member-info-panel__error",
                    style: "padding: 8px 12px; margin: 0 16px; background: var(--error-bg, #3a1a1a); color: var(--error-text, #f44336); border-radius: 6px; font-size: 13px;",
                    "{mod_error_text}"
                }
            }

            // Actions
            div {
                class: "member-info-panel__section",
                h4 { class: "member-info-panel__section-title", "Actions" }

                div {
                    class: "member-info-panel__actions",

                    button {
                        class: "member-info-panel__action-btn",
                        onclick: on_start_dm,
                        "Send Direct Message"
                    }

                    button {
                        class: "member-info-panel__action-btn member-info-panel__action-btn--secondary",
                        onclick: on_toggle_ignore,
                        if *is_ignored.read() { "Unignore User" } else { "Ignore User" }
                    }
                }
            }

            // Moderation actions
            div {
                class: "member-info-panel__section",
                h4 { class: "member-info-panel__section-title", "Moderation" }

                div {
                    class: "member-info-panel__actions",

                    button {
                        class: "member-info-panel__action-btn member-info-panel__action-btn--secondary",
                        onclick: on_mute,
                        title: "Set power level to -1 to prevent sending messages",
                        "Mute User"
                    }

                    button {
                        class: "member-info-panel__action-btn member-info-panel__action-btn--warning",
                        style: "color: var(--warning-text, #ff9800); border-color: var(--warning-text, #ff9800);",
                        onclick: on_kick,
                        "Kick from Room"
                    }

                    button {
                        class: "member-info-panel__action-btn member-info-panel__action-btn--danger",
                        style: "color: var(--error-text, #f44336); border-color: var(--error-text, #f44336);",
                        onclick: on_ban,
                        "Ban from Room"
                    }

                    {
                        let user_id_for_unban = user_id.clone();
                        rsx! {
                            button {
                                class: "member-info-panel__action-btn member-info-panel__action-btn--secondary",
                                onclick: move |_| {
                                    let uid = user_id_for_unban.clone();
                                    mod_error.set(None);
                                    spawn(async move {
                                        let (client, active_room_id) = {
                                            let s = state.read();
                                            (s.client.clone(), s.active_room_id.clone())
                                        };
                                        if let (Some(client), Some(room_id)) = (client, active_room_id) {
                                            if let Some(room) = client.get_room(&room_id) {
                                                if let Ok(target) = matrix_sdk::ruma::OwnedUserId::try_from(uid.as_str()) {
                                                    match room.unban_user(&target, Some("Unbanned by room admin")).await {
                                                        Ok(_) => {
                                                            tracing::info!("Unbanned user {target}");
                                                            state.write().right_panel = RightPanelView::MemberList;
                                                        }
                                                        Err(e) => {
                                                            tracing::error!("Failed to unban user: {e}");
                                                            mod_error.set(Some(format!("Unban failed: {e}")));
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    });
                                },
                                "Unban User"
                            }
                        }
                    }
                }
            }
        }
    }
}