summaryrefslogblamecommitdiffstats
path: root/src/views/login_page.rs
blob: 2edd4b5d7f10d29adb417cf4fe127f84462eb090 (plain) (tree)




























































































































































































                                                                                                                              
use std::{str::FromStr, sync::Arc};

use filamento::{db::Db, error::{CommandError, ConnectionError}, files::{opfs::OPFSError, FilesMem, FilesOPFS}, UpdateMessage};
use jid::JID;
use thiserror::Error;
use leptos::prelude::*;
use tokio::sync::mpsc::Receiver;
use tracing::debug;

use crate::{client::Client, files::Files};

use super::AppState;

#[derive(Clone, Debug, Error)]
pub enum LoginError {
    #[error("Missing Password")]
    MissingPassword,
    #[error("Missing JID")]
    MissingJID,
    #[error("Invalid JID: {0}")]
    InvalidJID(#[from] jid::ParseError),
    #[error("Connection Error: {0}")]
    ConnectionError(#[from] CommandError<ConnectionError>),
    #[error("OPFS: {0}")]
    OPFS(#[from] OPFSError),
}

#[component]
pub fn LoginPage(
    set_app: WriteSignal<AppState>,
    set_client: WriteSignal<Option<(Client, Receiver<UpdateMessage>)>>,
) -> impl IntoView {
    let jid = RwSignal::new("".to_string());
    let password = RwSignal::new("".to_string());
    let remember_me = RwSignal::new(false);
    let connect_on_login = RwSignal::new(true);

    let (error, set_error) = signal(None::<LoginError>);
    let error_message = move || {
        error.with(|error| {
            if let Some(error) = error {
                view! { <div class="error">{error.to_string()}</div> }.into_any()
            } else {
                view! {}.into_any()
            }
        })
    };

    let (login_pending, set_login_pending) = signal(false);

    let login = Action::new_local(move |_| {
        async move {
            set_login_pending.set(true);

            if jid.read_untracked().is_empty() {
                set_error.set(Some(LoginError::MissingJID));
                set_login_pending.set(false);
                return;
            }

            if password.read_untracked().is_empty() {
                set_error.set(Some(LoginError::MissingPassword));
                set_login_pending.set(false);
                return;
            }

            let jid = match JID::from_str(&jid.read_untracked()) {
                Ok(j) => j,
                Err(e) => {
                    set_error.set(Some(e.into()));
                    set_login_pending.set(false);
                    return;
                }
            };

            let remember_me = remember_me.get_untracked();
            // initialise the client
            let db = if remember_me {
                debug!("creating db in opfs");
                Db::create_connect_and_migrate(jid.as_bare().to_string())
                    .await
                    .unwrap()
            } else {
                debug!("creating db in memory");
                Db::create_connect_and_migrate_memory().await.unwrap()
            };
            let files = if remember_me {
                let opfs = FilesOPFS::new(jid.as_bare().to_string()).await;
                match opfs {
                    Ok(f) => Files::Opfs(f),
                    Err(e) => {
                        set_error.set(Some(e.into()));
                        set_login_pending.set(false);
                        return;
                    }
                }
            } else {
                Files::Mem(FilesMem::new())
            };
            let (client, updates) = filamento::Client::new(
                jid.clone(),
                password.read_untracked().clone(),
                db,
                files.clone(),
            );
            let resource = ArcRwSignal::new(None::<String>);
            let client = Client {
                client,
                resource: resource.clone(),
                jid: Arc::new(jid.to_bare()),
                file_store: files,
            };

            if *connect_on_login.read_untracked() {
                match client.connect().await {
                    Ok(r) => {
                        resource.set(Some(r))
                    }
                    Err(e) => {
                        set_error.set(Some(e.into()));
                        set_login_pending.set(false);
                        return;
                    }
                }
            }

            // debug!("before setting app state");
            set_client.set(Some((client, updates)));
            set_app.set(AppState::LoggedIn);
        }
    });

    view! {
        <div class="center fill">
            <div id="login-form" class="panel">
                <div id="hero">
                    <img src="/assets/macaw-icon.png" />
                    <h1>Macaw Instant Messenger</h1>
                </div>
                {error_message}
                <form on:submit=move |ev| {
                    ev.prevent_default();
                    login.dispatch(());
                }>
                    <label for="jid">JID</label>
                    <input
                        disabled=login_pending
                        placeholder="caw@macaw.chat"
                        type="text"
                        bind:value=jid
                        name="jid"
                        id="jid"
                        autofocus="true"
                    />
                    <label for="password">Password</label>
                    <input
                        disabled=login_pending
                        placeholder="••••••••"
                        type="password"
                        bind:value=password
                        name="password"
                        id="password"
                    />
                    <div>
                        <label for="remember_me">Remember me</label>
                        <input
                            disabled=login_pending
                            type="checkbox"
                            bind:checked=remember_me
                            name="remember_me"
                            id="remember_me"
                        />
                    </div>
                    <div>
                        <label for="connect_on_login">Connect on login</label>
                        <input
                            disabled=login_pending
                            type="checkbox"
                            bind:checked=connect_on_login
                            name="connect_on_login"
                            id="connect_on_login"
                        />
                    </div>
                    <input disabled=login_pending class="button" type="submit" value="Log In" />
                </form>
            </div>
        </div>
    }
}