From 66cbe6710041bd75c2c57d09ede87d773d65b28e Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Sun, 8 Mar 2026 20:49:57 -0700 Subject: [PATCH] fix(quotesdb): fix report submit button always disabled, add auth code hint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove captcha-gated disabled state on report submit button — Turnstile does not fire a DOM input event so captcha_solved never became true; rate limiting is handled at the WAF layer so the gate was incorrect - Token is still read from the hidden Turnstile input at submit time if present - Add explanatory hint below "Enter Auth Code" heading in AuthModal explaining the code was provided or generated when the quote was created - Add .auth-modal__hint CSS class for the hint text Co-Authored-By: Claude Sonnet 4.6 --- quotesdb/src/bin/ui/components/auth_modal.rs | 3 + .../src/bin/ui/components/report_modal.rs | 62 ++----------------- quotesdb/src/bin/ui/style.css | 6 ++ 3 files changed, 15 insertions(+), 56 deletions(-) diff --git a/quotesdb/src/bin/ui/components/auth_modal.rs b/quotesdb/src/bin/ui/components/auth_modal.rs index 3908c85..418a793 100644 --- a/quotesdb/src/bin/ui/components/auth_modal.rs +++ b/quotesdb/src/bin/ui/components/auth_modal.rs @@ -54,6 +54,9 @@ pub fn auth_modal(props: &AuthModalProps) -> Html {

{ "Enter Auth Code" }

+

+ { "This is the authorization code you were given (or that was generated) when you created this quote." } +

` /// with the appropriate `data-sitekey` attribute. Turnstile's global JS (loaded -/// in `index.html`) picks up that div automatically. +/// in `index.html`) picks up that div automatically. The token is read from the +/// hidden `cf-turnstile-response` input at submit time and forwarded to the +/// caller; it may be empty if Turnstile has not yet solved (e.g., no network). +/// Rate limiting is enforced at the WAF layer rather than the application layer. /// /// # Example /// @@ -55,8 +57,6 @@ pub struct ReportModalProps { #[function_component(ReportModal)] pub fn report_modal(props: &ReportModalProps) -> Html { let reason = use_state(String::new); - // Whether the Turnstile CAPTCHA has been solved (token is non-empty). - let captcha_solved = use_state(|| false); // --- Handle reason textarea input --- let on_reason_input = { @@ -71,50 +71,6 @@ pub fn report_modal(props: &ReportModalProps) -> Html { }) }; - // --- Poll the hidden Turnstile input for a token on the "input" event --- - // Turnstile fires a synthetic "input" event on the hidden field when solved. - // We wire this up via use_effect so we can attach the listener to the DOM. - { - let captcha_solved = captcha_solved.clone(); - use_effect(move || { - let window = web_sys::window().expect("no global window"); - let document = window.document().expect("no document"); - - // Closure to check the hidden input value. - let captcha_solved_clone = captcha_solved.clone(); - let check_fn = wasm_bindgen::closure::Closure::::new(move || { - let document = web_sys::window() - .and_then(|w| w.document()) - .expect("document"); - if let Some(input_el) = document - .query_selector("input[name='cf-turnstile-response']") - .ok() - .flatten() - { - let input: HtmlInputElement = input_el.unchecked_into(); - captcha_solved_clone.set(!input.value().is_empty()); - } - }); - - // Attach the listener to the document so it catches the event - // regardless of where Turnstile fires it. - let _ = document - .add_event_listener_with_callback("input", check_fn.as_ref().unchecked_ref()); - - // Also run once immediately in case the widget already solved - // (e.g., page revisit). - check_fn - .as_ref() - .unchecked_ref::() - .call0(&wasm_bindgen::JsValue::NULL) - .ok(); - - // Keep the closure alive until the component unmounts. - check_fn.forget(); - || () - }); - } - // --- Form submit handler --- let on_submit = { let reason = reason.clone(); @@ -122,7 +78,7 @@ pub fn report_modal(props: &ReportModalProps) -> Html { Callback::from(move |e: SubmitEvent| { e.prevent_default(); - // Read the Turnstile token from the hidden input. + // Read the Turnstile token from the hidden input if available. let token = web_sys::window() .and_then(|w| w.document()) .and_then(|doc| { @@ -133,10 +89,6 @@ pub fn report_modal(props: &ReportModalProps) -> Html { .map(|el| el.unchecked_into::().value()) .unwrap_or_default(); - if token.is_empty() { - return; // Should not happen — button is disabled until solved. - } - let reason_val = { let s = (*reason).clone(); if s.is_empty() { @@ -151,7 +103,6 @@ pub fn report_modal(props: &ReportModalProps) -> Html { }; let reason_len = reason.len(); - let submit_disabled = !*captcha_solved; let on_cancel = props.on_cancel.clone(); @@ -193,7 +144,6 @@ pub fn report_modal(props: &ReportModalProps) -> Html { diff --git a/quotesdb/src/bin/ui/style.css b/quotesdb/src/bin/ui/style.css index 715482a..c56d3fd 100644 --- a/quotesdb/src/bin/ui/style.css +++ b/quotesdb/src/bin/ui/style.css @@ -480,6 +480,12 @@ code { margin-bottom: 1rem; } +.auth-modal__hint { + font-size: 0.85rem; + color: var(--color-muted); + margin-bottom: 1rem; +} + .auth-modal__input { margin-bottom: 1rem; }