You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
11 KiB
Rust

//! Submit page — new quote submission form.
use crate::api::{self, ApiError};
use crate::components::error::ErrorDisplay;
use crate::storage;
use crate::Route;
use quotesdb::CreateQuoteInput;
use wasm_bindgen_futures::spawn_local;
use web_sys::HtmlInputElement;
use yew::prelude::*;
use yew_router::prelude::*;
/// Submit page component.
///
/// Provides a form for creating a new quote with fields for text, author,
/// source, date, tags, and an optional custom auth code. On success, displays
/// the returned auth code prominently so the user can save it, and stores it
/// in session storage for immediate use.
#[function_component(SubmitPage)]
pub fn submit_page() -> Html {
let text = use_state(String::new);
let author = use_state(String::new);
let source = use_state(String::new);
let date = use_state(String::new);
let tags = use_state(String::new);
let custom_auth = use_state(String::new);
let submitting = use_state(|| false);
let error: UseStateHandle<Option<String>> = use_state(|| None);
let success: UseStateHandle<Option<(String, String)>> = use_state(|| None); // (quote_id, auth_code)
let onsubmit = {
let text = text.clone();
let author = author.clone();
let source = source.clone();
let date = date.clone();
let tags = tags.clone();
let custom_auth = custom_auth.clone();
let submitting = submitting.clone();
let error = error.clone();
let success = success.clone();
Callback::from(move |e: SubmitEvent| {
e.prevent_default();
if *submitting {
return;
}
let text_val = (*text).clone();
let author_val = (*author).clone();
let source_val = (*source).clone();
let date_val = (*date).clone();
let tags_val = (*tags).clone();
let auth_val = (*custom_auth).clone();
if text_val.is_empty() {
error.set(Some("Quote text is required.".to_string()));
return;
}
let parsed_tags: Vec<String> = tags_val
.split(',')
.map(|t| t.trim().to_string())
.filter(|t| !t.is_empty())
.collect();
let input = CreateQuoteInput {
text: text_val,
author: if author_val.is_empty() {
"Anonymous".to_string()
} else {
author_val
},
source: if source_val.is_empty() {
None
} else {
Some(source_val)
},
date: if date_val.is_empty() {
None
} else {
Some(date_val)
},
tags: parsed_tags,
auth_code: if auth_val.is_empty() {
None
} else {
Some(auth_val)
},
};
submitting.set(true);
error.set(None);
let submitting = submitting.clone();
let error = error.clone();
let success = success.clone();
spawn_local(async move {
match api::create_quote(&input).await {
Ok(resp) => {
storage::set_auth_code(&resp.quote.id, &resp.auth_code);
success.set(Some((resp.quote.id, resp.auth_code)));
submitting.set(false);
}
Err(ApiError::Server { status, message }) => {
error.set(Some(format!("Error {status}: {message}")));
submitting.set(false);
}
Err(e) => {
error.set(Some(e.to_string()));
submitting.set(false);
}
}
});
})
};
if let Some((quote_id, auth_code)) = (*success).clone() {
return html! {
<div class="page-submit page-submit--success">
<h1 class="page-submit__title">{ "Quote Submitted!" }</h1>
<p>{ "Your quote has been added. Save your auth code below — you'll need it to edit or delete this quote." }</p>
<div class="page-submit__auth-code-box">
<strong>{ "Auth Code: " }</strong>
<code class="page-submit__auth-code">{ &auth_code }</code>
</div>
<div class="page-submit__actions">
<Link<Route>
to={Route::QuoteDetail { id: quote_id.clone() }}
classes="btn btn--primary"
>
{ "View your quote" }
</Link<Route>>
<Link<Route> to={Route::Browse} classes="btn">
{ "Browse all quotes" }
</Link<Route>>
</div>
</div>
};
}
html! {
<div class="page-submit">
<h1 class="page-submit__title">{ "Submit a Quote" }</h1>
if let Some(err) = (*error).clone() {
<ErrorDisplay message={err} />
}
<form class="submit-form" onsubmit={onsubmit}>
<div class="submit-form__field">
<label class="submit-form__label" for="text">{ "Quote text *" }</label>
<textarea
id="text"
class="submit-form__textarea"
placeholder="Enter the quote text..."
value={(*text).clone()}
oninput={{
let text = text.clone();
Callback::from(move |e: InputEvent| {
let el: web_sys::HtmlTextAreaElement = e.target_unchecked_into();
text.set(el.value());
})
}}
required=true
/>
</div>
<div class="submit-form__field">
<label class="submit-form__label" for="author">{ "Author (optional, defaults to Anonymous)" }</label>
<input
id="author"
class="submit-form__input"
type="text"
placeholder="e.g. Mark Twain"
value={(*author).clone()}
oninput={{
let author = author.clone();
Callback::from(move |e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
author.set(input.value());
})
}}
/>
</div>
<div class="submit-form__field">
<label class="submit-form__label" for="source">{ "Source (optional)" }</label>
<input
id="source"
class="submit-form__input"
type="text"
placeholder="e.g. Adventures of Huckleberry Finn"
value={(*source).clone()}
oninput={{
let source = source.clone();
Callback::from(move |e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
source.set(input.value());
})
}}
/>
</div>
<div class="submit-form__field">
<label class="submit-form__label" for="date">{ "Date (optional)" }</label>
<input
id="date"
class="submit-form__input"
type="date"
value={(*date).clone()}
oninput={{
let date = date.clone();
Callback::from(move |e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
date.set(input.value());
})
}}
/>
</div>
<div class="submit-form__field">
<label class="submit-form__label" for="tags">{ "Tags (optional, comma-separated)" }</label>
<input
id="tags"
class="submit-form__input"
type="text"
placeholder="e.g. humor, classic, american"
value={(*tags).clone()}
oninput={{
let tags = tags.clone();
Callback::from(move |e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
tags.set(input.value());
})
}}
/>
</div>
<div class="submit-form__field">
<label class="submit-form__label" for="auth_code">
{ "Edit/delete passphrase (auto-generated if left blank)" }
</label>
<input
id="auth_code"
class="submit-form__input"
type="text"
placeholder="word-word-word-word (auto-generated if empty)"
value={(*custom_auth).clone()}
oninput={{
let custom_auth = custom_auth.clone();
Callback::from(move |e: InputEvent| {
let input: HtmlInputElement = e.target_unchecked_into();
custom_auth.set(input.value());
})
}}
/>
</div>
<div class="submit-form__actions">
<button
type="submit"
class="btn btn--primary"
disabled={*submitting}
>
if *submitting {
{ "Submitting..." }
} else {
{ "Submit Quote" }
}
</button>
</div>
</form>
</div>
}
}