diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index d608aaf..04252bf 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -2,6 +2,8 @@ mod auth; mod pages; use crate::auth::ProtectedRoute; use crate::pages::*; +use gloo_net::http::Request; +use wasm_bindgen_futures::spawn_local; use yew::prelude::*; use yew_router::prelude::*; @@ -19,6 +21,8 @@ enum Route { Register, #[at("/login")] Login, + #[at("/setup")] + Setup, #[at("/users")] AllUsers, #[at("/users/:id")] @@ -49,6 +53,47 @@ fn sidebar_shell(props: &SidebarShellProps) -> Html { } } +#[derive(Properties, PartialEq)] +pub struct AdminCheckWrapperProps { + pub children: Children, +} + +#[component(AdminCheckWrapper)] +fn admin_check_wrapper(props: &AdminCheckWrapperProps) -> Html { + let admin_exists = use_state(|| None::); + let navigator = use_navigator().unwrap(); + + { + let admin_exists = admin_exists.clone(); + use_effect_with((), move |_| { + let admin_exists = admin_exists.clone(); + spawn_local(async move { + match Request::get("/api/check-admin").send().await { + Ok(resp) if resp.status() == 200 => { + if let Ok(data) = resp.json::().await { + let has_admin = data["has_admin"].as_bool().unwrap_or(false); + admin_exists.set(Some(has_admin)); + } + } + _ => { + admin_exists.set(Some(false)); + } + } + }); + || () + }); + } + + match *admin_exists { + None => html! {
{ "Loading..." }
}, + Some(false) => { + navigator.push(&Route::Setup); + html! {
{ "Redirecting to setup..." }
} + } + Some(true) => props.children.clone().into(), + } +} + fn switch(route: Route) -> Html { match route { Route::Home => html! { @@ -87,7 +132,14 @@ fn switch(route: Route) -> Html { }, - Route::Login => html! { }, + Route::Login => html! { + + + + }, + Route::Setup => html! { + + }, Route::AllUsers => html! { diff --git a/frontend/src/pages/mod.rs b/frontend/src/pages/mod.rs index edab625..6147f33 100644 --- a/frontend/src/pages/mod.rs +++ b/frontend/src/pages/mod.rs @@ -1,4 +1,5 @@ pub mod basic_pages; +pub mod setup; pub mod sidebar; pub mod ticket; pub mod user; diff --git a/frontend/src/pages/setup.rs b/frontend/src/pages/setup.rs new file mode 100644 index 0000000..ba0d70b --- /dev/null +++ b/frontend/src/pages/setup.rs @@ -0,0 +1,233 @@ +use gloo_net::http::Request; +use serde::{Deserialize, Serialize}; +use wasm_bindgen_futures::spawn_local; +use yew::prelude::*; +use yew_router::prelude::*; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AdminSetupScheme { + pub first_name: String, + pub last_name: String, + pub username: String, + pub pwd: String, +} + +#[component(InitialAdminSetup)] +pub fn initial_admin_setup() -> Html { + let first_name = use_state(|| "".to_string()); + let last_name = use_state(|| "".to_string()); + let username = use_state(|| "".to_string()); + let pwd = use_state(|| "".to_string()); + let pwd_confirm = use_state(|| "".to_string()); + let error = use_state(|| String::new()); + let success = use_state(|| false); + let loading = use_state(|| false); + let admin_check_done = use_state(|| false); + let navigator = use_navigator().unwrap(); + + { + let admin_check_done = admin_check_done.clone(); + let navigator = navigator.clone(); + use_effect_with((), move |_| { + let admin_check_done = admin_check_done.clone(); + let navigator = navigator.clone(); + spawn_local(async move { + match Request::get("/api/check-admin").send().await { + Ok(resp) if resp.status() == 200 => { + if let Ok(data) = resp.json::().await { + let has_admin = data["has_admin"].as_bool().unwrap_or(false); + if has_admin { + navigator.push(&crate::Route::Login); + } else { + admin_check_done.set(true); + } + } else { + admin_check_done.set(true); + } + } + _ => { + admin_check_done.set(true); + } + } + }); + || () + }); + } + + if !*admin_check_done { + return html! {
{ "Checking..." }
}; + } + + let onsubmit = { + let first_name = first_name.clone(); + let last_name = last_name.clone(); + let username = username.clone(); + let pwd = pwd.clone(); + let pwd_confirm = pwd_confirm.clone(); + let error = error.clone(); + let success = success.clone(); + let loading = loading.clone(); + let navigator = navigator.clone(); + + Callback::from(move |e: SubmitEvent| { + e.prevent_default(); + + if (*pwd).is_empty() || (*pwd_confirm).is_empty() { + error.set("Password fields cannot be empty".to_string()); + return; + } + + if *pwd != *pwd_confirm { + error.set("Passwords do not match".to_string()); + return; + } + + if (*username).is_empty() { + error.set("Username cannot be empty".to_string()); + return; + } + + let first_name_val = (*first_name).clone(); + let last_name_val = (*last_name).clone(); + let username_val = (*username).clone(); + let pwd_val = (*pwd).clone(); + + loading.set(true); + error.set(String::new()); + success.set(false); + + let error = error.clone(); + let success = success.clone(); + let loading = loading.clone(); + let navigator = navigator.clone(); + + spawn_local(async move { + let payload = AdminSetupScheme { + first_name: first_name_val, + last_name: last_name_val, + username: username_val, + pwd: pwd_val, + }; + + let response = Request::post("/api/setup-admin") + .header("Content-Type", "application/json") + .json(&payload) + .unwrap() + .send() + .await; + + loading.set(false); + + match response { + Ok(r) if r.status() == 200 => { + success.set(true); + navigator.push(&crate::Route::Login); + } + Ok(r) => { + let text = r.text().await.unwrap_or_else(|_| "unknown".into()); + error.set(format!("HTTP {}: {}", r.status(), text)); + } + Err(err) => error.set(format!("Network error: {}", err)), + } + }); + }) + }; + + html! { +
+
+

{ "Initial Admin Setup" }

+

{ "Create your first administrator account" }

+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + if !error.is_empty() { +

{ (*error).clone() }

+ } + + if *success { +

{ "Admin account created successfully! Redirecting to login..." }

+ } +
+
+
+ } +}