use leptos::prelude::*;
use leptos::task::spawn_local;
use leptos_router::hooks::use_navigate;
use crate::app::{login, register};
pub fn get_cookie(name: &str) -> Option<String> {
#[cfg(feature = "hydrate")]
{
use wasm_bindgen::JsCast;
let document = web_sys::window()?.document()?;
let html_doc: web_sys::HtmlDocument = document.dyn_into().ok()?;
let cookies = html_doc.cookie().ok()?;
cookies
.split(';')
.filter_map(|c| {
let (key, value) = c.trim().split_once('=')?;
(key == name).then(|| value.to_owned())
})
.next()
}
#[cfg(not(feature = "hydrate"))]
{
let _ = name;
None
}
}
pub fn set_cookie(name: &str, value: &str) {
#[cfg(feature = "hydrate")]
{
use wasm_bindgen::JsCast;
if let Some(Ok(html_doc)) = web_sys::window()
.and_then(|w| w.document())
.map(|d| d.dyn_into::<web_sys::HtmlDocument>())
{
let _ = html_doc.set_cookie(&format!(
"{name}={value}; path=/; max-age=86400; SameSite=Strict"
));
}
}
#[cfg(not(feature = "hydrate"))]
{
let _ = (name, value);
}
}
pub fn remove_cookie(name: &str) {
#[cfg(feature = "hydrate")]
{
use wasm_bindgen::JsCast;
if let Some(Ok(html_doc)) = web_sys::window()
.and_then(|w| w.document())
.map(|d| d.dyn_into::<web_sys::HtmlDocument>())
{
let _ = html_doc.set_cookie(&format!("{name}=; path=/; max-age=0"));
}
}
#[cfg(not(feature = "hydrate"))]
{
let _ = name;
}
}
#[component]
pub fn LoginPage() -> impl IntoView {
let username = RwSignal::new(String::new());
let password = RwSignal::new(String::new());
let error = RwSignal::new(Option::<String>::None);
let success = RwSignal::new(Option::<String>::None);
let pending = RwSignal::new(false);
let is_register = RwSignal::new(false);
let navigate = use_navigate();
Effect::new({
let navigate = navigate.clone();
move |_| {
if get_cookie("session_token").is_some() {
navigate("/content", Default::default());
}
}
});
let on_submit = move |ev: leptos::ev::SubmitEvent| {
ev.prevent_default();
let navigate = navigate.clone();
pending.set(true);
error.set(None);
success.set(None);
if is_register.get() {
spawn_local(async move {
match register(username.get(), password.get()).await {
Ok(()) => {
success.set(Some("Account created, you can now log in".into()));
is_register.set(false);
pending.set(false);
}
Err(e) => {
error.set(Some(e.to_string()));
pending.set(false);
}
}
});
} else {
spawn_local(async move {
match login(username.get(), password.get()).await {
Ok(token) => {
set_cookie("session_token", &token);
navigate("/content", Default::default());
}
Err(_) => {
error.set(Some("Invalid username or password".into()));
pending.set(false);
}
}
});
}
};
let disabled = move || pending.get() || username.get().is_empty() || password.get().is_empty();
view! {
<div class="container">
<div class="card">
<h1>"WebApp.rs"</h1>
<p class="subtitle">"A web application completely written in Rust"</p>
<form on:submit=on_submit>
<div class="field">
<input
type="text"
placeholder="Username"
prop:value=username
on:input=move |ev| username.set(event_target_value(&ev))
/>
</div>
<div class="field">
<input
type="password"
placeholder="Password"
prop:value=password
on:input=move |ev| password.set(event_target_value(&ev))
/>
</div>
{move || {
error
.get()
.map(|msg| {
view! { <div class="error">{msg}</div> }
})
}}
{move || {
success
.get()
.map(|msg| {
view! { <div class="success">{msg}</div> }
})
}}
<button type="submit" disabled=disabled>
{move || {
if pending.get() {
if is_register.get() { "Registering..." } else { "Logging in..." }
} else if is_register.get() {
"Register"
} else {
"Login"
}
}}
</button>
</form>
<p class="toggle">
{move || {
if is_register.get() {
"Already have an account? "
} else {
"Don't have an account? "
}
}}
<a
href="#"
on:click=move |ev| {
ev.prevent_default();
is_register.set(!is_register.get());
error.set(None);
success.set(None);
}
>
{move || if is_register.get() { "Login" } else { "Register" }}
</a>
</p>
</div>
</div>
}
}