diff --git a/src/app.rs b/src/app.rs index 993bb9e..d5efaf9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,9 +2,10 @@ use leptos::prelude::*; use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title}; use leptos_router::{ components::{Route, Router, Routes}, - StaticSegment, + ParamSegment, StaticSegment, }; +use crate::codes::{HomePage, SubmitPage}; use crate::manage::ManagePage; pub fn shell(options: LeptosOptions) -> impl IntoView { @@ -36,13 +37,18 @@ pub fn App() -> impl IntoView { // sets the document title - + <Title text="Submit a code"/> // content for this welcome page <Router> <main> <Routes fallback=|| "Page not found.".into_view()> + <Route path=StaticSegment("") view=HomePage/> <Route path=StaticSegment("manage") view=ManagePage/> + <Route + path=(StaticSegment("submit"), ParamSegment("code")) + view=SubmitPage + /> </Routes> </main> </Router> diff --git a/src/codes/code1.rs b/src/codes/code1.rs new file mode 100644 index 0000000..7e167cf --- /dev/null +++ b/src/codes/code1.rs @@ -0,0 +1,5 @@ +pub const CODE: &str = "Qj4eAn"; +pub const LIVE_MESSAGE: &str = "Welcome! Your code has been accepted."; +pub const NOT_LIVE_MESSAGE: &str = "Thanks for submitting your code. Please try again later."; + +pub(super) fn on_submit(_live: bool) {} diff --git a/src/codes/mod.rs b/src/codes/mod.rs new file mode 100644 index 0000000..8db2b20 --- /dev/null +++ b/src/codes/mod.rs @@ -0,0 +1,105 @@ +use leptos::prelude::*; +use leptos_router::{ + components::A, + hooks::{use_navigate, use_params_map}, + NavigateOptions, +}; + +#[cfg(feature = "ssr")] +mod code1; + +#[server] +async fn submit_code(code: String) -> Result<String, ServerFnError> { + let live = crate::manage::server_live_state(); + let message = if code == crate::codes::code1::CODE { + crate::codes::code1::on_submit(live); + + if live { + crate::codes::code1::LIVE_MESSAGE + } else { + crate::codes::code1::NOT_LIVE_MESSAGE + } + } else { + "This code is not recognized." + }; + + Ok(message.to_string()) +} + +fn encode_path_segment(value: &str) -> String { + value + .bytes() + .map(|byte| { + if byte.is_ascii_alphanumeric() || b"-_.~".contains(&byte) { + (byte as char).to_string() + } else { + format!("%{byte:02X}") + } + }) + .collect() +} + +#[component] +pub fn HomePage() -> impl IntoView { + let (code, set_code) = signal(String::new()); + let navigate = use_navigate(); + + let on_submit = move |event: leptos::ev::SubmitEvent| { + event.prevent_default(); + let code = encode_path_segment(code.get_untracked().trim()); + if !code.is_empty() { + navigate(&format!("/submit/{code}"), NavigateOptions::default()); + } + }; + + view! { + <section class="status-card"> + <p class="eyebrow">"CODE SUBMISSION"</p> + <h1>"Submit a code"</h1> + <p class="intro">"Enter your code to continue."</p> + <form class="submit-form" on:submit=on_submit> + <label class="key-label" for="submission-code">"Code"</label> + <input + id="submission-code" + class="key-input" + type="text" + placeholder="Enter your code" + autocomplete="off" + required + prop:value=code + on:input=move |event| set_code.set(event_target_value(&event)) + /> + <button class="status-button go-live" type="submit">"Submit"</button> + </form> + </section> + } +} + +#[component] +pub fn SubmitPage() -> impl IntoView { + let params = use_params_map(); + let submitted_code = move || params.with(|params| params.get("code").unwrap_or_default()); + let submission = Resource::new(move || submitted_code(), |code| submit_code(code)); + + view! { + <section class="status-card"> + <p class="eyebrow">"CODE SUBMISSION"</p> + <Suspense fallback=|| view! { <p class="loading">"Checking code..."</p> }> + {move || Suspend::new(async move { + match submission.await { + Ok(message) => view! { + <h1>"Submission result"</h1> + <p class="intro">{message}</p> + }.into_any(), + Err(error) => view! { + <p class="error-message" role="alert"> + "Unable to check code: " {error.to_string()} + </p> + }.into_any(), + } + })} + </Suspense> + <A href="/" attr:class="home-link">"Back to home"</A> + </section> + } +} diff --git a/src/lib.rs b/src/lib.rs index c52c809..b9d274a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod app; +pub mod codes; pub mod manage; #[cfg(feature = "hydrate")] diff --git a/src/manage.rs b/src/manage.rs index 642e8d6..55d91d3 100644 --- a/src/manage.rs +++ b/src/manage.rs @@ -1,4 +1,5 @@ use leptos::prelude::*; +use leptos_router::components::A; #[cfg(feature = "ssr")] use std::sync::atomic::{AtomicBool, Ordering}; @@ -6,12 +7,17 @@ use std::sync::atomic::{AtomicBool, Ordering}; #[cfg(feature = "ssr")] static SERVER_LIVE: AtomicBool = AtomicBool::new(false); +#[cfg(feature = "ssr")] +pub(crate) fn server_live_state() -> bool { + SERVER_LIVE.load(Ordering::SeqCst) +} + #[cfg(feature = "ssr")] const STATUS_SECRET_KEY: &str = "password"; #[server] async fn get_live_state() -> Result<bool, ServerFnError> { - Ok(SERVER_LIVE.load(Ordering::SeqCst)) + Ok(server_live_state()) } #[server] @@ -46,6 +52,7 @@ pub fn ManagePage() -> impl IntoView { } })} </Suspense> + <A href="/" attr:class="home-link">"Back to home"</A> </section> } } diff --git a/style/main.scss b/style/main.scss index d659011..9bc3e7b 100644 --- a/style/main.scss +++ b/style/main.scss @@ -50,6 +50,11 @@ h1 { gap: 12px; } +.submit-form { + display: grid; + gap: 12px; +} + .key-label { color: #d3dceb; font-size: 0.9rem; @@ -155,6 +160,20 @@ h1 { line-height: 1.5; } +.home-link { + display: inline-block; + margin-top: 24px; + color: #8bbaff; + font-size: 0.9rem; + font-weight: 600; + text-decoration: none; +} + +.home-link:hover { + color: #b3d2ff; + text-decoration: underline; +} + @media (prefers-reduced-motion: reduce) { * { transition-duration: 0.01ms !important;