diff --git a/src/components/forms.rs b/src/components/forms.rs new file mode 100644 index 0000000..ec85a05 --- /dev/null +++ b/src/components/forms.rs @@ -0,0 +1,54 @@ +use dioxus::prelude::*; + +pub const INPUT_CLASS: &str = "w-full px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600"; + +pub const BUTTON_PRIMARY_CLASS: &str = "w-full py-2 px-4 bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 font-medium rounded-full hover:opacity-80 transition-opacity cursor-pointer"; + +pub const BUTTON_SECONDARY_CLASS: &str = "px-6 py-2 bg-gray-200 dark:bg-[#333] text-gray-700 dark:text-[#dadadb] rounded-full font-medium hover:opacity-80 transition-opacity cursor-pointer"; + +#[component] +pub fn FormInput( + r#type: &'static str, + placeholder: &'static str, + value: String, + oninput: EventHandler, + onkeydown: Option>, +) -> Element { + rsx! { + input { + class: "{INPUT_CLASS}", + r#type: "{r#type}", + placeholder: "{placeholder}", + value: "{value}", + oninput: move |e| oninput.call(e.value()), + onkeydown: move |e| { + if let Some(ref handler) = onkeydown { + handler.call(e); + } + }, + } + } +} + +#[component] +pub fn FormLabel(label: &'static str) -> Element { + rsx! { + label { class: "block text-sm font-medium text-gray-700 dark:text-[#9b9c9d] mb-1", + "{label}" + } + } +} + +#[component] +pub fn AlertBox(message: String, variant: &'static str) -> Element { + let (bg_class, text_class) = match variant { + "error" => ("bg-red-100 dark:bg-red-900/30", "text-red-700 dark:text-red-300"), + "success" => ("bg-green-100 dark:bg-green-900/30", "text-green-700 dark:text-green-300"), + _ => ("bg-gray-100 dark:bg-[#333]", "text-gray-700 dark:text-[#9b9c9d]"), + }; + rsx! { + div { class: "mb-4 p-3 {bg_class} {text_class} rounded-lg text-center", + "{message}" + } + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index e15eeb3..116b971 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,6 +1,7 @@ pub mod admin_layout; pub mod admin_skeleton; pub mod footer; +pub mod forms; pub mod frontend_layout; pub mod header; pub mod nav; diff --git a/src/pages/login.rs b/src/pages/login.rs index 81df2c6..a4b8e3c 100644 --- a/src/pages/login.rs +++ b/src/pages/login.rs @@ -2,6 +2,7 @@ use dioxus::prelude::*; use dioxus::router::components::Link; use crate::api::auth::{login, AuthResponse}; +use crate::components::forms::{AlertBox, FormInput, FormLabel, BUTTON_PRIMARY_CLASS}; use crate::router::Route; #[component] @@ -54,40 +55,32 @@ pub fn Login() -> Element { } if let Some(err) = error() { - div { class: "mb-4 p-3 bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg text-center", - "{err}" - } + AlertBox { message: err, variant: "error" } } div { class: "space-y-4", div { - label { class: "block text-sm font-medium text-gray-700 dark:text-[#9b9c9d] mb-1", - "用户名 / 邮箱" - } - input { - class: "w-full px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600", + FormLabel { label: "用户名 / 邮箱" } + FormInput { r#type: "text", placeholder: "用户名或邮箱", value: username(), - oninput: move |e| username.set(e.value()), - onkeydown: move |e| if e.key() == Key::Enter { on_submit(()) }, + oninput: move |v: String| username.set(v), + onkeydown: Some(EventHandler::new(move |e: KeyboardEvent| if e.key() == Key::Enter { on_submit(()) })), } } div { - label { class: "block text-sm font-medium text-gray-700 dark:text-[#9b9c9d] mb-1", - "密码" - } - input { - class: "w-full px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600", + FormLabel { label: "密码" } + FormInput { r#type: "password", placeholder: "密码", value: password(), - oninput: move |e| password.set(e.value()), - onkeydown: move |e| if e.key() == Key::Enter { on_submit(()) }, + oninput: move |v: String| password.set(v), + onkeydown: Some(EventHandler::new(move |e: KeyboardEvent| if e.key() == Key::Enter { on_submit(()) })), } } button { - class: "w-full py-2 px-4 bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 font-medium rounded-full hover:opacity-80 transition-opacity", + class: "{BUTTON_PRIMARY_CLASS}", onclick: move |_| on_submit(()), "登录" } diff --git a/src/pages/register.rs b/src/pages/register.rs index 09676a5..f0ed4fc 100644 --- a/src/pages/register.rs +++ b/src/pages/register.rs @@ -2,6 +2,7 @@ use dioxus::prelude::*; use dioxus::router::components::Link; use crate::api::auth::{register, AuthResponse}; +use crate::components::forms::{AlertBox, FormInput, FormLabel, BUTTON_PRIMARY_CLASS}; use crate::router::Route; #[component] @@ -60,72 +61,63 @@ pub fn Register() -> Element { } if success() { - div { class: "mb-4 p-3 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 rounded-lg text-center", - "注册成功!" - Link { class: "block mt-2 text-gray-700 dark:text-[#dadadb] hover:underline cursor-pointer", - to: Route::Login {}, - "去登录" - } + AlertBox { + message: "注册成功!".to_string(), + variant: "success", + } + Link { class: "block text-center mt-2 text-gray-700 dark:text-[#dadadb] hover:underline cursor-pointer", + to: Route::Login {}, + "去登录" } } if let Some(err) = error() { - div { class: "mb-4 p-3 bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-lg text-center", - "{err}" - } + AlertBox { message: err, variant: "error" } } div { class: "space-y-4", div { - label { class: "block text-sm font-medium text-gray-700 dark:text-[#9b9c9d] mb-1", - "用户名" - } - input { - class: "w-full px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600", + FormLabel { label: "用户名" } + FormInput { r#type: "text", placeholder: "3-50 位字符", value: username(), - oninput: move |e| username.set(e.value()), + oninput: move |v: String| username.set(v), + onkeydown: None, } } div { - label { class: "block text-sm font-medium text-gray-700 dark:text-[#9b9c9d] mb-1", - "邮箱" - } - input { - class: "w-full px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600", + FormLabel { label: "邮箱" } + FormInput { r#type: "email", placeholder: "your@email.com", value: email(), - oninput: move |e| email.set(e.value()), + oninput: move |v: String| email.set(v), + onkeydown: None, } } div { - label { class: "block text-sm font-medium text-gray-700 dark:text-[#9b9c9d] mb-1", - "密码" - } - input { - class: "w-full px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600", + FormLabel { label: "密码" } + FormInput { r#type: "password", placeholder: "至少 8 位", value: password(), - oninput: move |e| password.set(e.value()), + oninput: move |v: String| password.set(v), + onkeydown: None, } } div { - label { class: "block text-sm font-medium text-gray-700 dark:text-[#9b9c9d] mb-1", - "确认密码" - } - input { - class: "w-full px-4 py-2 border border-gray-200 dark:border-[#333] rounded-lg bg-white dark:bg-[#2e2e33] text-gray-900 dark:text-[#dadadb] focus:outline-none focus:border-gray-400 dark:focus:border-gray-600", + FormLabel { label: "确认密码" } + FormInput { r#type: "password", placeholder: "再次输入密码", value: confirm_password(), - oninput: move |e| confirm_password.set(e.value()), + oninput: move |v: String| confirm_password.set(v), + onkeydown: None, } } button { - class: "w-full py-2 px-4 bg-gray-900 dark:bg-[#dadadb] text-white dark:text-gray-900 font-medium rounded-full hover:opacity-80 transition-opacity", + class: "{BUTTON_PRIMARY_CLASS}", onclick: on_submit, "注册" }