synpad 0.1.0

A full-featured Matrix chat client built with Dioxus
use dioxus::prelude::*;

use crate::app::Route;
use crate::client;
use crate::state::app_state::{AppState, AuthStatus};

/// Login form component with homeserver, username, and password fields.
#[component]
pub fn LoginForm() -> Element {
    let mut state = use_context::<Signal<AppState>>();
    let navigator = use_navigator();

    let mut homeserver = use_signal(|| "https://matrix.org".to_string());
    let mut username = use_signal(|| String::new());
    let mut password = use_signal(|| String::new());
    let mut error_message = use_signal(|| Option::<String>::None);
    let mut is_loading = use_signal(|| false);

    let on_submit = move |evt: Event<FormData>| {
        evt.prevent_default();

        let hs = homeserver.read().clone();
        let user = username.read().clone();
        let pass = password.read().clone();

        if user.is_empty() || pass.is_empty() {
            error_message.set(Some("Please enter username and password".to_string()));
            return;
        }

        is_loading.set(true);
        error_message.set(None);
        state.write().auth_status = AuthStatus::LoggingIn;

        spawn(async move {
            match client::login_with_password(&hs, &user, &pass).await {
                Ok(matrix_client) => {
                    tracing::info!("Login successful");

                    // Set client and auth status in one atomic write.
                    // The App component watches for client and starts the sync loop.
                    {
                        let mut w = state.write();
                        w.auth_status = AuthStatus::LoggedIn;
                        w.client = Some(matrix_client);
                    }

                    navigator.push(Route::Home {});
                }
                Err(e) => {
                    tracing::error!("Login failed: {e}");
                    let msg = if e.to_string().contains("403") {
                        "Invalid username or password".to_string()
                    } else {
                        format!("Login failed: {e}")
                    };
                    error_message.set(Some(msg));
                    state.write().auth_status = AuthStatus::Error(e.to_string());
                }
            }
            is_loading.set(false);
        });
    };

    rsx! {
        form {
            class: "login-form",
            onsubmit: on_submit,

            h2 { class: "login-form__title", "Sign In" }

            // Error message
            if let Some(ref err) = *error_message.read() {
                div {
                    class: "login-form__error",
                    "{err}"
                }
            }

            // Homeserver field
            div {
                class: "login-form__field",
                label {
                    r#for: "homeserver",
                    "Homeserver"
                }
                input {
                    id: "homeserver",
                    r#type: "url",
                    placeholder: "https://matrix.org",
                    value: "{homeserver}",
                    oninput: move |evt| homeserver.set(evt.value()),
                    disabled: *is_loading.read(),
                }
            }

            // Username field
            div {
                class: "login-form__field",
                label {
                    r#for: "username",
                    "Username"
                }
                input {
                    id: "username",
                    r#type: "text",
                    placeholder: "@user:matrix.org",
                    value: "{username}",
                    oninput: move |evt| username.set(evt.value()),
                    disabled: *is_loading.read(),
                    autofocus: true,
                }
            }

            // Password field
            div {
                class: "login-form__field",
                label {
                    r#for: "password",
                    "Password"
                }
                input {
                    id: "password",
                    r#type: "password",
                    placeholder: "Password",
                    value: "{password}",
                    oninput: move |evt| password.set(evt.value()),
                    disabled: *is_loading.read(),
                }
            }

            // Submit button
            button {
                r#type: "submit",
                class: "login-form__submit",
                disabled: *is_loading.read(),
                if *is_loading.read() {
                    "Signing in..."
                } else {
                    "Sign In"
                }
            }
        }
    }
}