synpad 0.1.0

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

use crate::components::toast::{ToastContainer, ToastState};
use crate::layouts::auth_layout::AuthLayout;
use crate::layouts::main_layout::MainLayout;
use crate::pages::forgot_password::ForgotPassword;
use crate::pages::home::Home;
use crate::pages::login::LoginPage;
use crate::pages::register::RegisterPage;
use crate::pages::settings::SettingsPage;
use crate::pages::welcome::Welcome;
use crate::state::app_state::{AppState, AuthStatus};

/// Application routes
#[derive(Routable, Clone, Debug, PartialEq)]
#[rustfmt::skip]
pub enum Route {
    #[layout(AuthLayout)]
        #[route("/login")]
        LoginPage {},
        #[route("/register")]
        RegisterPage {},
        #[route("/forgot-password")]
        ForgotPassword {},
    #[end_layout]

    #[layout(MainLayout)]
        #[route("/")]
        Home {},
        #[route("/welcome")]
        Welcome {},
        #[route("/settings")]
        SettingsPage {},
}

/// Root application component
pub fn App() -> Element {
    // Initialize global application state
    let app_state = use_context_provider(|| Signal::new(AppState::default()));
    use_context_provider(|| Signal::new(ToastState::default()));

    let mut state = app_state;

    // Spawn session restoration on mount
    use_effect(move || {
        spawn(async move {
            tracing::info!("Attempting to restore previous session...");
            match crate::persistence::matrix_state::load_session().await {
                Ok(Some(session_data)) => {
                    tracing::info!("Found saved session, restoring...");
                    match crate::client::restore_session(session_data).await {
                        Ok(client) => {
                            // Verify the session is actually valid by attempting a sync
                            let sync_settings = matrix_sdk::config::SyncSettings::default()
                                .timeout(std::time::Duration::from_secs(10));
                            match client.sync_once(sync_settings).await {
                                Ok(_) => {
                                    tracing::info!("Session restored and verified");
                                    let mut w = state.write();
                                    w.auth_status = AuthStatus::LoggedIn;
                                    w.client = Some(client);
                                }
                                Err(e) => {
                                    tracing::warn!("Saved session is invalid: {e}");
                                    let _ = crate::persistence::matrix_state::clear_session().await;
                                    state.write().auth_status = AuthStatus::LoggedOut;
                                }
                            }
                        }
                        Err(e) => {
                            tracing::warn!("Failed to restore session: {e}");
                            state.write().auth_status = AuthStatus::LoggedOut;
                        }
                    }
                }
                Ok(None) => {
                    tracing::info!("No saved session found");
                    state.write().auth_status = AuthStatus::LoggedOut;
                }
                Err(e) => {
                    tracing::error!("Error loading session: {e}");
                    state.write().auth_status = AuthStatus::LoggedOut;
                }
            }
        });
    });

    // Watch for client availability and start sync loop from App (which never unmounts).
    // This ensures the sync loop survives navigation between pages.
    let mut sync_running = use_signal(|| false);
    use_effect(move || {
        let has_client = state.read().client.is_some();
        let already_running = *sync_running.read();

        if has_client && !already_running {
            let client = state.read().client.clone().unwrap();
            sync_running.set(true);
            tracing::info!("App: client detected, starting sync loop");
            spawn(async move {
                crate::client::sliding_sync::start_sync_loop(client, state).await;
            });
        }
    });

    rsx! {
        document::Style { {include_str!("../assets/css/main.css")} }
        Router::<Route> {}
        ToastContainer {}
    }
}