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};

/// Registration form component.
#[component]
pub fn RegisterForm() -> 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 confirm_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();
        let confirm = confirm_password.read().clone();

        if user.trim().is_empty() {
            error_message.set(Some("Please enter a username".to_string()));
            return;
        }

        if pass.len() < 8 {
            error_message.set(Some("Password must be at least 8 characters".to_string()));
            return;
        }

        if pass != confirm {
            error_message.set(Some("Passwords do not match".to_string()));
            return;
        }

        is_loading.set(true);
        error_message.set(None);

        spawn(async move {
            match register_account(&hs, &user, &pass).await {
                Ok(matrix_client) => {
                    tracing::info!("Registration successful");
                    {
                        let mut w = state.write();
                        w.auth_status = AuthStatus::LoggedIn;
                        w.client = Some(matrix_client);
                    }
                    navigator.push(Route::Home {});
                }
                Err(e) => {
                    tracing::error!("Registration failed: {e}");
                    error_message.set(Some(e));
                    is_loading.set(false);
                }
            }
        });
    };

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

            h2 { class: "register-form__title", "Create Account" }

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

            div {
                class: "register-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(),
                }
            }

            div {
                class: "register-form__field",
                label { r#for: "reg-username", "Username" }
                input {
                    id: "reg-username",
                    r#type: "text",
                    placeholder: "Choose a username",
                    value: "{username}",
                    oninput: move |evt| username.set(evt.value()),
                    disabled: *is_loading.read(),
                }
            }

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

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

            button {
                r#type: "submit",
                class: "register-form__submit",
                disabled: *is_loading.read(),
                if *is_loading.read() {
                    "Creating account..."
                } else {
                    "Create Account"
                }
            }
        }
    }
}

/// Register a new account on the homeserver.
async fn register_account(
    homeserver: &str,
    username: &str,
    password: &str,
) -> Result<matrix_sdk::Client, String> {
    let matrix_client = client::build_client(homeserver)
        .await
        .map_err(|e| format!("Failed to connect to homeserver: {e}"))?;

    // Use the UIAA (User-Interactive Authentication API) registration
    use matrix_sdk::ruma::api::client::account::register;

    let mut request = register::v3::Request::new();
    request.username = Some(username.to_string());
    request.password = Some(password.to_string());
    request.initial_device_display_name = Some("Netrix".to_string());

    match matrix_client.send(request).await {
        Ok(response) => {
            tracing::info!("Registered user: {:?}", response.user_id);

            // After registration, log in with the credentials to get a proper session
            let client = client::login_with_password(homeserver, username, password)
                .await
                .map_err(|e| format!("Registered but failed to log in: {e}"))?;

            Ok(client)
        }
        Err(e) => {
            let err_str = e.to_string();
            // Parse common registration errors
            if err_str.contains("M_USER_IN_USE") {
                Err("Username is already taken".to_string())
            } else if err_str.contains("M_EXCLUSIVE") {
                Err("This username is reserved".to_string())
            } else if err_str.contains("M_FORBIDDEN") {
                Err("Registration is disabled on this homeserver".to_string())
            } else if err_str.contains("interactive auth") || err_str.contains("401") {
                Err("This homeserver requires additional verification (CAPTCHA/email). Please register via the web.".to_string())
            } else {
                Err(format!("Registration failed: {e}"))
            }
        }
    }
}