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