feat: finalize toast implementation
This commit is contained in:
1 parent
a31c44b794
commit
0742e833da
4 files changed
+72
-65
No files matched your search
@@ -57,8 +57,8 @@ pub fn OutlinedTextField(
|
|||||||
#[prop(optional, into)] placeholder: String,
|
#[prop(optional, into)] placeholder: String,
|
||||||
#[prop(into)] data_member: String, // should this be an RwSignal??
|
#[prop(into)] data_member: String, // should this be an RwSignal??
|
||||||
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||||
#[prop(optional)] input_type: TextFieldType,
|
#[prop(optional, into)] input_type: Signal<TextFieldType>,
|
||||||
#[prop(into, optional)] disabled: Signal<bool>,
|
#[prop(optional, into)] disabled: Signal<bool>,
|
||||||
#[prop(optional, into)] name: String,
|
#[prop(optional, into)] name: String,
|
||||||
#[prop(optional, into)] label: String,
|
#[prop(optional, into)] label: String,
|
||||||
#[prop(optional, into)] required: Signal<bool>
|
#[prop(optional, into)] required: Signal<bool>
|
||||||
@@ -69,13 +69,20 @@ pub fn OutlinedTextField(
|
|||||||
let validator_context = use_validation_context()
|
let validator_context = use_validation_context()
|
||||||
.expect("FormValidSignal was not found");
|
.expect("FormValidSignal was not found");
|
||||||
|
|
||||||
let raw_value = RwSignal::new(data_map.get_untracked()
|
let raw_value = Signal::derive({
|
||||||
.get(&data_member)
|
let data_member = data_member.clone();
|
||||||
.unwrap_or(&ValueType::String(None))
|
move ||
|
||||||
.to_string());
|
data_map
|
||||||
|
.get()
|
||||||
|
.get(&data_member)
|
||||||
|
.unwrap_or(&ValueType::String(None))
|
||||||
|
.to_string()
|
||||||
|
});
|
||||||
|
|
||||||
let dirty = RwSignal::new(false);
|
let dirty = RwSignal::new(false);
|
||||||
let error = RwSignal::new(validate(required.get_untracked(), &raw_value.get_untracked(), &input_type));
|
let error = Signal::derive(move ||
|
||||||
|
validate(required.get(), &raw_value.get(), &input_type.get())
|
||||||
|
);
|
||||||
let show_errors = Signal::derive(move || {
|
let show_errors = Signal::derive(move || {
|
||||||
dirty.get() && matches!(error.get(), ValidationState::Invalid(_))
|
dirty.get() && matches!(error.get(), ValidationState::Invalid(_))
|
||||||
});
|
});
|
||||||
@@ -90,10 +97,10 @@ pub fn OutlinedTextField(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let str_type = match input_type {
|
let str_type = Signal::derive(move || match input_type.get_untracked() {
|
||||||
TextFieldType::Number => "number",
|
TextFieldType::Number => "number",
|
||||||
_ => "text" // This is fine even for emails, as we're doing our own validation.
|
_ => "text" // This is fine even for emails, as we're doing our own validation.
|
||||||
};
|
});
|
||||||
|
|
||||||
let on_blur = move |_| {
|
let on_blur = move |_| {
|
||||||
// Check validity of input's current value. The value will be updated by on_input,
|
// Check validity of input's current value. The value will be updated by on_input,
|
||||||
@@ -107,9 +114,7 @@ pub fn OutlinedTextField(
|
|||||||
let data_member = data_member.clone();
|
let data_member = data_member.clone();
|
||||||
move |e| {
|
move |e| {
|
||||||
let to_parse = event_target_value(&e);
|
let to_parse = event_target_value(&e);
|
||||||
raw_value.set(to_parse.clone());
|
let into_map = match input_type.get() {
|
||||||
error.set(validate(required.get(), &to_parse, &input_type));
|
|
||||||
let into_map = match input_type {
|
|
||||||
TextFieldType::Number => ValueType::Number(Some(to_parse)),
|
TextFieldType::Number => ValueType::Number(Some(to_parse)),
|
||||||
_ => ValueType::String(Some(to_parse))
|
_ => ValueType::String(Some(to_parse))
|
||||||
};
|
};
|
||||||
@@ -137,11 +142,7 @@ pub fn OutlinedTextField(
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
prop:name=name
|
prop:name=name
|
||||||
prop:value=move || data_map
|
prop:value=raw_value
|
||||||
.get() // HashMap
|
|
||||||
.get(&data_member) // Option<ValueType>
|
|
||||||
.unwrap_or(&ValueType::String(None)) // ValueType
|
|
||||||
.to_string()
|
|
||||||
on:input=on_input
|
on:input=on_input
|
||||||
on:blur=on_blur
|
on:blur=on_blur
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_animate::animate;
|
use leptos_animate::animate;
|
||||||
use leptos_animate::animations::classes::In;
|
use leptos_animate::animations::classes::{In, Out};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Toast(
|
pub fn Toast(
|
||||||
@@ -17,11 +17,19 @@ pub fn Toast(
|
|||||||
);
|
);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="flex flex-col rounded-md shadow-md"
|
<div
|
||||||
use:animate=In::default()
|
class="flex flex-col rounded-md shadow-lg/33 bg-white p-3 m-1.5 mx-auto min-w-lg max-w-lg"
|
||||||
.source("opacity-0 -translate-y-2 -scale-10")
|
use:animate=(
|
||||||
.active("transition-all duration-150")
|
In::default()
|
||||||
.target("opacity-100")>
|
.source("opacity-0 -translate-y-2 scale-90")
|
||||||
|
.active("transition-all duration-150 transform-gpu")
|
||||||
|
.target("opacity-100 translate-y-0 scale-100"),
|
||||||
|
Out::default()
|
||||||
|
.source("opacity-100 translate-y-0 scale-100")
|
||||||
|
.active("transition-all duration-150 transform-gpu")
|
||||||
|
.target("opacity-0 -translate-y-2 scale-90"),
|
||||||
|
)
|
||||||
|
>
|
||||||
<h3 class="font-bold text-lg">{title}</h3>
|
<h3 class="font-bold text-lg">{title}</h3>
|
||||||
<p>{message}</p>
|
<p>{message}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub fn ToastList(
|
|||||||
|
|
||||||
view! {
|
view! {
|
||||||
{children()}
|
{children()}
|
||||||
<div class="fixed top-1 left-1/2 right-1/2 -translate-x-1/2 min-w-lg">
|
<div class="fixed top-1 left-1/2 right-1/2 -translate-x-1/2 w-full">
|
||||||
<For
|
<For
|
||||||
each=move || context.messages.get()
|
each=move || context.messages.get()
|
||||||
key=|toast| toast.id.clone()
|
key=|toast| toast.id.clone()
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ use leptos::prelude::*;
|
|||||||
use leptos_oidc::{AuthLoaded, Authenticated};
|
use leptos_oidc::{AuthLoaded, Authenticated};
|
||||||
use leptos_router::hooks::{use_navigate, use_params};
|
use leptos_router::hooks::{use_navigate, use_params};
|
||||||
use crate::common::{ExpandableInfo, ScholarshipFormParams, SubmitStatus, ValueType};
|
use crate::common::{ExpandableInfo, ScholarshipFormParams, SubmitStatus, ValueType};
|
||||||
use crate::components::{ActionButton, Banner, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row, TextFieldType, ValidatedForm};
|
use crate::components::{ActionButton, Banner, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row, TextFieldType, Toast, ToastContext, ToastList, ValidatedForm};
|
||||||
use super::UnauthenticatedPage;
|
use super::UnauthenticatedPage;
|
||||||
use crate::pages::utils::get_user_claims;
|
use crate::pages::utils::get_user_claims;
|
||||||
use super::api::{get_comparison_info, get_provider_scholarships, get_scholarship_info, CreateScholarshipInfo, RegisterScholarship, DeleteProviderScholarship, get_comparisons_categorized, CreateTestComparisons};
|
use super::api::{get_provider_scholarships, get_scholarship_info, CreateScholarshipInfo, RegisterScholarship, DeleteProviderScholarship, get_comparisons_categorized, CreateTestComparisons};
|
||||||
|
|
||||||
|
|
||||||
/// # Scholarship Info Page
|
/// # Scholarship Info Page
|
||||||
@@ -45,26 +45,28 @@ pub fn ScholarshipInfoPage() -> impl IntoView {
|
|||||||
logo="/PHS_Stacked_Acronym.png"
|
logo="/PHS_Stacked_Acronym.png"
|
||||||
path="/"
|
path="/"
|
||||||
/>
|
/>
|
||||||
<AuthLoaded fallback=Loading>
|
<ToastList>
|
||||||
<Authenticated unauthenticated=UnauthenticatedPage>
|
<AuthLoaded fallback=Loading>
|
||||||
<div class="flex flex-row align-top items-start">
|
<Authenticated unauthenticated=UnauthenticatedPage>
|
||||||
<div class="flex flex-col flex-1" />
|
<div class="flex flex-row align-top items-start">
|
||||||
<ScholarshipList
|
<div class="flex flex-col flex-1" />
|
||||||
refresh_token=list_refresh
|
<ScholarshipList
|
||||||
on_delete=on_delete
|
refresh_token=list_refresh
|
||||||
/>
|
on_delete=on_delete
|
||||||
<Transition fallback=Loading>
|
|
||||||
<ScholarshipForm
|
|
||||||
scholarship_id=scholarship_id
|
|
||||||
on_submit=move || {
|
|
||||||
list_refresh.update(|n| *n += 1);
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Transition>
|
<Transition fallback=Loading>
|
||||||
<div class="flex flex-col flex-1" />
|
<ScholarshipForm
|
||||||
</div>
|
scholarship_id=scholarship_id
|
||||||
</Authenticated>
|
on_submit=move || {
|
||||||
</AuthLoaded>
|
list_refresh.update(|n| *n += 1);
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<div class="flex flex-col flex-1" />
|
||||||
|
</div>
|
||||||
|
</Authenticated>
|
||||||
|
</AuthLoaded>
|
||||||
|
</ToastList>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,15 +351,6 @@ fn ScholarshipForm(
|
|||||||
/// submission fails, this function is not run.
|
/// submission fails, this function is not run.
|
||||||
#[prop(into)] on_submit: Callback<()>
|
#[prop(into)] on_submit: Callback<()>
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
// let result_msg = Signal::derive(move || {
|
|
||||||
// match submit_status.get() {
|
|
||||||
// SubmitStatus::Idle => "".into(),
|
|
||||||
// SubmitStatus::Sending => "Sending submission...".into(),
|
|
||||||
// SubmitStatus::Success => "Success".into(),
|
|
||||||
// SubmitStatus::Error(msg) => format!("Error: {}", msg),
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
let submit_action = ServerAction::<CreateScholarshipInfo>::new();
|
let submit_action = ServerAction::<CreateScholarshipInfo>::new();
|
||||||
|
|
||||||
let submit_status = RwSignal::new(SubmitStatus::Idle);
|
let submit_status = RwSignal::new(SubmitStatus::Idle);
|
||||||
@@ -382,17 +375,6 @@ fn ScholarshipForm(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let comparison_list = Resource::new(
|
|
||||||
move || scholarship_id.get(), // There's no input to this function.
|
|
||||||
async move |_| {
|
|
||||||
get_comparison_info().await
|
|
||||||
.unwrap_or_else(|e| {
|
|
||||||
log!("Failed to get comparison info: {:?}", e);
|
|
||||||
Vec::new()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let comparison_lists = Resource::new(
|
let comparison_lists = Resource::new(
|
||||||
move || scholarship_id.get().is_some(),
|
move || scholarship_id.get().is_some(),
|
||||||
async move |_| {
|
async move |_| {
|
||||||
@@ -476,6 +458,8 @@ fn ScholarshipForm(
|
|||||||
// fafsa_required, award_to, transcript_required, recipient_selection, essay_requirement (and prompt)
|
// fafsa_required, award_to, transcript_required, recipient_selection, essay_requirement (and prompt)
|
||||||
// award_night_remarks
|
// award_night_remarks
|
||||||
|
|
||||||
|
let mut toasts = expect_context::<ToastContext>();
|
||||||
|
|
||||||
let on_submit = move |_| {
|
let on_submit = move |_| {
|
||||||
let mut info = ExpandableInfo::new(scholarship_id.get().unwrap_or_default());
|
let mut info = ExpandableInfo::new(scholarship_id.get().unwrap_or_default());
|
||||||
info.data = form_data.get();
|
info.data = form_data.get();
|
||||||
@@ -486,6 +470,20 @@ fn ScholarshipForm(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Effect::new(move || {
|
||||||
|
submit_action.value().get().is_some()
|
||||||
|
.then(|| {
|
||||||
|
log!("Displaying toasts!");
|
||||||
|
toasts.toast(
|
||||||
|
Toast::new()
|
||||||
|
.id(uuid::Uuid::new_v4())
|
||||||
|
.msg("Succesfully submitted.")
|
||||||
|
);
|
||||||
|
|
||||||
|
submit_action.clear();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let create_comps = ServerAction::<CreateTestComparisons>::new();
|
let create_comps = ServerAction::<CreateTestComparisons>::new();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
|||||||
Reference in new issue
Block a user