Implement shared RFC 5545 VEvent library with workspace restructuring
- Created calendar-models/ shared library with RFC 5545-compliant VEvent structures - Migrated backend to use shared VEvent with proper field mappings (dtstart/dtend, rrule, exdate, etc.) - Converted CalDAV client to parse into VEvent structures with structured types - Updated all CRUD handlers to use VEvent with CalendarUser, Attendee, VAlarm types - Restructured project as Cargo workspace with frontend/, backend/, calendar-models/ - Updated Trunk configuration for new directory structure - Fixed all compilation errors and field references throughout codebase - Updated documentation and build instructions for workspace structure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
206
frontend/src/components/login.rs
Normal file
206
frontend/src/components/login.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use yew::prelude::*;
|
||||
use web_sys::HtmlInputElement;
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct LoginProps {
|
||||
pub on_login: Callback<String>, // Callback with JWT token
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Login(props: &LoginProps) -> Html {
|
||||
let server_url = use_state(String::new);
|
||||
let username = use_state(String::new);
|
||||
let password = use_state(String::new);
|
||||
let error_message = use_state(|| Option::<String>::None);
|
||||
let is_loading = use_state(|| false);
|
||||
|
||||
let server_url_ref = use_node_ref();
|
||||
let username_ref = use_node_ref();
|
||||
let password_ref = use_node_ref();
|
||||
|
||||
let on_server_url_change = {
|
||||
let server_url = server_url.clone();
|
||||
Callback::from(move |e: Event| {
|
||||
let target = e.target_unchecked_into::<HtmlInputElement>();
|
||||
server_url.set(target.value());
|
||||
})
|
||||
};
|
||||
|
||||
let on_username_change = {
|
||||
let username = username.clone();
|
||||
Callback::from(move |e: Event| {
|
||||
let target = e.target_unchecked_into::<HtmlInputElement>();
|
||||
username.set(target.value());
|
||||
})
|
||||
};
|
||||
|
||||
let on_password_change = {
|
||||
let password = password.clone();
|
||||
Callback::from(move |e: Event| {
|
||||
let target = e.target_unchecked_into::<HtmlInputElement>();
|
||||
password.set(target.value());
|
||||
})
|
||||
};
|
||||
|
||||
let on_submit = {
|
||||
let server_url = server_url.clone();
|
||||
let username = username.clone();
|
||||
let password = password.clone();
|
||||
let error_message = error_message.clone();
|
||||
let is_loading = is_loading.clone();
|
||||
let on_login = props.on_login.clone();
|
||||
|
||||
Callback::from(move |e: SubmitEvent| {
|
||||
e.prevent_default();
|
||||
|
||||
let server_url = (*server_url).clone();
|
||||
let username = (*username).clone();
|
||||
let password = (*password).clone();
|
||||
let error_message = error_message.clone();
|
||||
let is_loading = is_loading.clone();
|
||||
let on_login = on_login.clone();
|
||||
|
||||
// Basic client-side validation
|
||||
if server_url.trim().is_empty() || username.trim().is_empty() || password.is_empty() {
|
||||
error_message.set(Some("Please fill in all fields".to_string()));
|
||||
return;
|
||||
}
|
||||
|
||||
is_loading.set(true);
|
||||
error_message.set(None);
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
web_sys::console::log_1(&"🚀 Starting login process...".into());
|
||||
match perform_login(server_url.clone(), username.clone(), password.clone()).await {
|
||||
Ok((token, credentials)) => {
|
||||
web_sys::console::log_1(&"✅ Login successful!".into());
|
||||
// Store token and credentials in local storage
|
||||
if let Err(_) = LocalStorage::set("auth_token", &token) {
|
||||
error_message.set(Some("Failed to store authentication token".to_string()));
|
||||
is_loading.set(false);
|
||||
return;
|
||||
}
|
||||
if let Err(_) = LocalStorage::set("caldav_credentials", &credentials) {
|
||||
error_message.set(Some("Failed to store credentials".to_string()));
|
||||
is_loading.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
is_loading.set(false);
|
||||
on_login.emit(token);
|
||||
}
|
||||
Err(err) => {
|
||||
web_sys::console::log_1(&format!("❌ Login failed: {}", err).into());
|
||||
error_message.set(Some(err));
|
||||
is_loading.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class="login-container">
|
||||
<div class="login-form">
|
||||
<h2>{"Sign In to CalDAV"}</h2>
|
||||
<form onsubmit={on_submit}>
|
||||
<div class="form-group">
|
||||
<label for="server_url">{"CalDAV Server URL"}</label>
|
||||
<input
|
||||
ref={server_url_ref}
|
||||
type="text"
|
||||
id="server_url"
|
||||
placeholder="https://your-caldav-server.com/dav/"
|
||||
value={(*server_url).clone()}
|
||||
onchange={on_server_url_change}
|
||||
disabled={*is_loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username">{"Username"}</label>
|
||||
<input
|
||||
ref={username_ref}
|
||||
type="text"
|
||||
id="username"
|
||||
placeholder="Enter your username"
|
||||
value={(*username).clone()}
|
||||
onchange={on_username_change}
|
||||
disabled={*is_loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">{"Password"}</label>
|
||||
<input
|
||||
ref={password_ref}
|
||||
type="password"
|
||||
id="password"
|
||||
placeholder="Enter your password"
|
||||
value={(*password).clone()}
|
||||
onchange={on_password_change}
|
||||
disabled={*is_loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
if let Some(error) = (*error_message).clone() {
|
||||
html! { <div class="error-message">{error}</div> }
|
||||
} else {
|
||||
html! {}
|
||||
}
|
||||
}
|
||||
|
||||
<button type="submit" disabled={*is_loading} class="login-button">
|
||||
{
|
||||
if *is_loading {
|
||||
"Signing in..."
|
||||
} else {
|
||||
"Sign In"
|
||||
}
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-links">
|
||||
<p>{"Enter your CalDAV server credentials to connect to your calendar"}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform login using the CalDAV auth service
|
||||
async fn perform_login(server_url: String, username: String, password: String) -> Result<(String, String), String> {
|
||||
use crate::auth::{AuthService, CalDAVLoginRequest};
|
||||
use serde_json;
|
||||
|
||||
web_sys::console::log_1(&format!("📡 Creating auth service and request...").into());
|
||||
|
||||
let auth_service = AuthService::new();
|
||||
let request = CalDAVLoginRequest {
|
||||
server_url: server_url.clone(),
|
||||
username: username.clone(),
|
||||
password: password.clone()
|
||||
};
|
||||
|
||||
web_sys::console::log_1(&format!("🚀 Sending login request to backend...").into());
|
||||
|
||||
match auth_service.login(request).await {
|
||||
Ok(response) => {
|
||||
web_sys::console::log_1(&format!("✅ Backend responded successfully").into());
|
||||
// Create credentials object to store
|
||||
let credentials = serde_json::json!({
|
||||
"server_url": server_url,
|
||||
"username": username,
|
||||
"password": password
|
||||
});
|
||||
Ok((response.token, credentials.to_string()))
|
||||
},
|
||||
Err(err) => {
|
||||
web_sys::console::log_1(&format!("❌ Backend error: {}", err).into());
|
||||
Err(err)
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user