implement code submission feature with routing and server validation
This commit is contained in:
+8
-2
@@ -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 {
|
||||
<Stylesheet id="leptos" href="/pkg/code_submission.css"/>
|
||||
|
||||
// sets the document title
|
||||
<Title text="Manage Server"/>
|
||||
<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>
|
||||
|
||||
@@ -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) {}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod app;
|
||||
pub mod codes;
|
||||
pub mod manage;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
||||
+8
-1
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user