Compare commits

...
12 Commits
Author SHA1 Message Date
Lucas Vas 448e8bbb38 Merge pull request #68 from R15-Software-Dev/fix/comparison-changes
Comparison and sports changes
2026-03-11 12:49:53 -04:00
alphafetus ae7a17ad92 chore: add volleyball option 2026-03-11 11:54:34 -04:00
alphafetus 750059e9ad fix: attended_bas checks correct data member 2026-03-11 11:53:50 -04:00
alphafetus 5a371ae574 chore: temporarily reopen provider forms (for real this time) 2026-03-11 11:53:05 -04:00
alphafetus d7f44463c6 chore: temporarily reopen provider forms 2026-03-11 11:33:30 -04:00
alphafetus 5872002538 feat: improve error messages in files api 2026-03-11 11:28:52 -04:00
alphafetus 957fc0c5eb chore: change title of form 2026-03-09 12:16:41 -04:00
Lucas Vas 8a2d5b0494 Merge pull request #67 from R15-Software-Dev/hotfix/66-minor-changes
Minor changes to student forms
2026-03-06 13:58:33 -05:00
alphafetus 07fae5878f feat: add toasts and reactive feedback to forms on submission
Users were not able to tell that they had submitted before this.
2026-03-06 13:46:35 -05:00
alphafetus def5c8696b chore: update intended major field 2026-03-06 13:23:51 -05:00
alphafetus 7c1e281749 chore: add due date reminder to home page 2026-03-06 13:22:28 -05:00
alphafetus 49bed2f1e1 fix: correct gender data member 2026-03-06 13:21:18 -05:00
14 changed files with 160 additions and 69 deletions

No files matched your search

+7 -7
View File
@@ -1,11 +1,11 @@
use crate::components::ToastList; use crate::components::ToastList;
use crate::components::login::ProviderLoginContext; use crate::components::login::ProviderLoginContext;
use crate::pages::student;
use crate::pages::{ use crate::pages::{
AdminHomePage, AdminProviderPage, AdminScholarshipPage, AdminShell, AdminUtilsPage, AdminHomePage, AdminProviderPage, AdminScholarshipPage, AdminShell, AdminUtilsPage,
ApplicantsPageFallback, AuthCallbackPage, LoanerBorrowForm, LoanerFallback, LoanerReturnForm, ApplicantsPageFallback, AuthCallbackPage, LoanerBorrowForm, LoanerFallback, LoanerReturnForm,
LoanerShell, ProviderContactPage, ProviderPortal, LoanerShell, ProviderContactPage, ProviderPortal, ScholarshipInfoPage,
}; };
use crate::pages::{FormClosedPage, student};
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context}; use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context};
use leptos_oidc::AuthSignal; use leptos_oidc::AuthSignal;
@@ -89,11 +89,11 @@ fn ProviderRoutes() -> impl MatchNestedRoutes + Clone {
<Route path=path!("/applicants") view=ApplicantsPageFallback /> <Route path=path!("/applicants") view=ApplicantsPageFallback />
<Route path=path!("/callback") view=AuthCallbackPage /> <Route path=path!("/callback") view=AuthCallbackPage />
<Route path=path!("/profile") view=ProviderContactPage /> <Route path=path!("/profile") view=ProviderContactPage />
<Route path=path!("/scholarships") view=FormClosedPage /> // <Route path=path!("/scholarships") view=FormClosedPage />
// <ParentRoute path=path!("/scholarships") view=FormClosedPage> <ParentRoute path=path!("/scholarships") view=ScholarshipInfoPage>
// <Route path=path!(":id") view=FormClosedPage /> <Route path=path!(":id") view=ScholarshipInfoPage />
// <Route path=path!("") view=FormClosedPage /> <Route path=path!("") view=ScholarshipInfoPage />
// </ParentRoute> </ParentRoute>
</ParentRoute> </ParentRoute>
} }
.into_inner() .into_inner()
+3
View File
@@ -111,6 +111,8 @@ pub fn ValidatedForm(
#[prop(into)] title: Signal<String>, #[prop(into)] title: Signal<String>,
/// The description for the included header. /// The description for the included header.
#[prop(optional, into)] description: Signal<String>, #[prop(optional, into)] description: Signal<String>,
/// A `Signal` that determines when the submit button should be disabled.
#[prop(optional, into)] disabled: Signal<bool>,
) -> impl IntoView { ) -> impl IntoView {
let validators = RwSignal::new(vec![]); let validators = RwSignal::new(vec![]);
@@ -154,6 +156,7 @@ pub fn ValidatedForm(
<Header title=title description=description /> <Header title=title description=description />
{children()} {children()}
<ActionButton <ActionButton
disabled=disabled
on:click=move |_| { on:click=move |_| {
log!("{:?}", can_submit.get()); log!("{:?}", can_submit.get());
} }
+31 -42
View File
@@ -14,10 +14,9 @@ use crate::common::{
ComparisonData, ComparisonType, MapListComparison, NumberComparison, NumberListComparison, ComparisonData, ComparisonType, MapListComparison, NumberComparison, NumberListComparison,
TextComparison, TextListComparison, ValueType, TextComparison, TextListComparison, ValueType,
}; };
use std::collections::HashMap;
use leptos::prelude::ServerFnError; use leptos::prelude::ServerFnError;
use leptos::server; use leptos::server;
use std::collections::HashMap;
/// Helper function to create all sports comparisons. Only used to create initial /// Helper function to create all sports comparisons. Only used to create initial
/// comparison data, and should be removed upon completion of a comparison editor page. /// comparison data, and should be removed upon completion of a comparison editor page.
@@ -250,14 +249,18 @@ fn create_major_comparisons() -> Vec<ComparisonData> {
let ids = display_texts let ids = display_texts
.iter() .iter()
.map(|text| .map(|text| {
format!("{}_{}", "major", text.to_lowercase().replace(" ", "_").replace("/", "_")) format!(
) "{}_{}",
"major",
text.to_lowercase().replace(" ", "_").replace("/", "_")
)
})
.collect::<Vec<String>>(); .collect::<Vec<String>>();
ids.iter().zip(display_texts).map(|(id, display_text)| { ids.iter()
create_major_comparison(id, display_text) .zip(display_texts)
}) .map(|(id, display_text)| create_major_comparison(id, display_text))
.collect::<Vec<ComparisonData>>() .collect::<Vec<ComparisonData>>()
} }
@@ -280,13 +283,11 @@ fn make_comp_list() -> Vec<ComparisonData> {
"Academic Information", "Academic Information",
"Math SAT Score > 650", "Math SAT Score > 650",
); );
let service_hours_20 = ComparisonData::new( let service_hours_20 = ComparisonData::new(
"service_hours_20", "service_hours_20",
"service_hours", "service_hours",
ComparisonType::Number( ComparisonType::Number(NumberComparison::GreaterThanOrEqual),
NumberComparison::GreaterThanOrEqual
),
ValueType::Number(Some(20.to_string())), ValueType::Number(Some(20.to_string())),
"Community Service", "Community Service",
"20+ service hours", "20+ service hours",
@@ -295,9 +296,7 @@ fn make_comp_list() -> Vec<ComparisonData> {
let service_hours_25 = ComparisonData::new( let service_hours_25 = ComparisonData::new(
"service_hours_25", "service_hours_25",
"service_hours", "service_hours",
ComparisonType::Number( ComparisonType::Number(NumberComparison::GreaterThanOrEqual),
NumberComparison::GreaterThanOrEqual
),
ValueType::Number(Some(25.to_string())), ValueType::Number(Some(25.to_string())),
"Community Service", "Community Service",
"25+ service hours", "25+ service hours",
@@ -306,9 +305,7 @@ fn make_comp_list() -> Vec<ComparisonData> {
let service_hours_30 = ComparisonData::new( let service_hours_30 = ComparisonData::new(
"service_hours_30", "service_hours_30",
"service_hours", "service_hours",
ComparisonType::Number( ComparisonType::Number(NumberComparison::GreaterThanOrEqual),
NumberComparison::GreaterThanOrEqual
),
ValueType::Number(Some(30.to_string())), ValueType::Number(Some(30.to_string())),
"Community Service", "Community Service",
"30+ service hours", "30+ service hours",
@@ -334,7 +331,7 @@ fn make_comp_list() -> Vec<ComparisonData> {
let attended_bas = ComparisonData::new( let attended_bas = ComparisonData::new(
"attended_bas", "attended_bas",
"attended_bas", "attend_bas",
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Yes".to_string())), ValueType::String(Some("Yes".to_string())),
"Additional Eligibility Factors", "Additional Eligibility Factors",
@@ -365,7 +362,7 @@ fn make_comp_list() -> Vec<ComparisonData> {
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Yes".to_string())), ValueType::String(Some("Yes".to_string())),
"Additional Eligibility Factors", "Additional Eligibility Factors",
"Participated in Pomperaug Youth Baseball" "Participated in Pomperaug Youth Baseball",
); );
let aquatic_club = ComparisonData::new( let aquatic_club = ComparisonData::new(
@@ -374,25 +371,25 @@ fn make_comp_list() -> Vec<ComparisonData> {
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Yes".to_string())), ValueType::String(Some("Yes".to_string())),
"Additional Eligibility Factors", "Additional Eligibility Factors",
"Participated in Panthers Aquatic Club" "Participated in Panthers Aquatic Club",
); );
let pea_member = ComparisonData::new( let pea_member = ComparisonData::new(
"pea_member", "pea_member",
"pea_member", "pea_member",
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Yes".to_string())), ValueType::String(Some("Yes".to_string())),
"Additional Eligibility Factors", "Additional Eligibility Factors",
"Has a Parent in the PEA" "Has a Parent in the PEA",
); );
let music_program = ComparisonData::new( let music_program = ComparisonData::new(
"music_program", "music_program",
"music_program", "music_program",
ComparisonType::Text(TextComparison::Matches), ComparisonType::Text(TextComparison::Matches),
ValueType::String(Some("Yes".to_string())), ValueType::String(Some("Yes".to_string())),
"Sports Participation", "Sports Participation",
"PHS Music Program" "PHS Music Program",
); );
let mut sports_comps = create_sports_comparisons(); let mut sports_comps = create_sports_comparisons();
@@ -412,7 +409,7 @@ fn make_comp_list() -> Vec<ComparisonData> {
youth_baseball, youth_baseball,
aquatic_club, aquatic_club,
pea_member, pea_member,
music_program music_program,
]; ];
comp_list.append(&mut sports_comps); comp_list.append(&mut sports_comps);
@@ -453,12 +450,7 @@ pub async fn get_comparison_info() -> Result<Vec<ComparisonData>, ServerFnError>
// Query the database for all comparisons. The client is only going to use the // Query the database for all comparisons. The client is only going to use the
// id and display text, but we'll return the whole thing. // id and display text, but we'll return the whole thing.
match client match client.scan().table_name(COMPARISONS_TABLE).send().await {
.scan()
.table_name(COMPARISONS_TABLE)
.send()
.await
{
Ok(output) => { Ok(output) => {
if let Some(items) = output.items { if let Some(items) = output.items {
log!("Found comparisons from API: {:?}", items); log!("Found comparisons from API: {:?}", items);
@@ -475,16 +467,12 @@ pub async fn get_comparison_info() -> Result<Vec<ComparisonData>, ServerFnError>
} }
#[server] #[server]
pub async fn get_comparisons_categorized() -> Result<HashMap<String, Vec<ComparisonData>>, ServerFnError> { pub async fn get_comparisons_categorized()
-> Result<HashMap<String, Vec<ComparisonData>>, ServerFnError> {
let client = create_dynamo_client().await; let client = create_dynamo_client().await;
log!("Getting all comparisons from the database, by category."); log!("Getting all comparisons from the database, by category.");
match client match client.scan().table_name(COMPARISONS_TABLE).send().await {
.scan()
.table_name(COMPARISONS_TABLE)
.send()
.await
{
Ok(output) => { Ok(output) => {
if let Some(items) = output.items { if let Some(items) = output.items {
log!("Found comparisons, categorizing."); log!("Found comparisons, categorizing.");
@@ -492,14 +480,15 @@ pub async fn get_comparisons_categorized() -> Result<HashMap<String, Vec<Compari
let items: Vec<ComparisonData> = serde_dynamo::from_items(items)?; let items: Vec<ComparisonData> = serde_dynamo::from_items(items)?;
let categorized = items.iter().fold( let categorized = items.iter().fold(
HashMap::<String, Vec<ComparisonData>>::new(), |mut map, comp| { HashMap::<String, Vec<ComparisonData>>::new(),
// Load each comparison into a map, then return that map. |mut map, comp| {
// Load each comparison into a map, then return that map.
map.entry(comp.category.clone()) map.entry(comp.category.clone())
.and_modify(|vec| vec.push(comp.clone())) .and_modify(|vec| vec.push(comp.clone()))
.or_insert(vec![comp.clone()]); .or_insert(vec![comp.clone()]);
map map
} },
); );
Ok(categorized) Ok(categorized)
+23 -6
View File
@@ -7,7 +7,11 @@ mod imports {
pub use super::super::{MAIN_TABLE_NAME, S3_BUCKET_NAME}; pub use super::super::{MAIN_TABLE_NAME, S3_BUCKET_NAME};
pub use crate::pages::api::tokens::validate_and_get_token_info; pub use crate::pages::api::tokens::validate_and_get_token_info;
pub use crate::utils::server::create_aws_config; pub use crate::utils::server::create_aws_config;
pub use aws_sdk_dynamodb::types::{AttributeValue, ReturnValue, Select}; pub use aws_sdk_dynamodb::{
error::ProvideErrorMetadata as _,
types::{AttributeValue, ReturnValue, Select},
};
pub use aws_sdk_s3::error::ProvideErrorMetadata as _;
} }
#[server(input = MultipartFormData)] #[server(input = MultipartFormData)]
@@ -57,6 +61,9 @@ pub async fn upload_file(data: MultipartData) -> Result<String, ServerFnError> {
if input_name.is_empty() { if input_name.is_empty() {
return Err(ServerFnError::new("Missing input file")); return Err(ServerFnError::new("Missing input file"));
} }
if file_bytes.is_empty() {
return Err(ServerFnError::new("Missing file contents"));
}
let user_claims = validate_and_get_token_info(access_token).await?; let user_claims = validate_and_get_token_info(access_token).await?;
let subject = user_claims.subject; let subject = user_claims.subject;
@@ -75,7 +82,12 @@ pub async fn upload_file(data: MultipartData) -> Result<String, ServerFnError> {
.send() .send()
.await .await
.map(|_| ()) .map(|_| ())
.map_err(|e| ServerFnError::new(format!("Couldn't put file to S3: {}", e.to_string())))?; .map_err(|e| {
ServerFnError::new(format!(
"Couldn't put file to S3: {}",
e.message().unwrap_or_default()
))
})?;
// Besides uploading the file to S3, we also want to store a FILE entry in Dynamo. // Besides uploading the file to S3, we also want to store a FILE entry in Dynamo.
// This will allow the server to quickly return a list of all files that a specific user has // This will allow the server to quickly return a list of all files that a specific user has
@@ -104,7 +116,7 @@ pub async fn upload_file(data: MultipartData) -> Result<String, ServerFnError> {
.map_err(|e| { .map_err(|e| {
ServerFnError::new(format!( ServerFnError::new(format!(
"Couldn't put file to Dynamo, file rolled back: {}", "Couldn't put file to Dynamo, file rolled back: {}",
e.to_string() e.message().unwrap_or_default()
)) ))
}); });
@@ -119,7 +131,7 @@ pub async fn upload_file(data: MultipartData) -> Result<String, ServerFnError> {
.map_err(|e| { .map_err(|e| {
ServerFnError::new(format!( ServerFnError::new(format!(
"Couldn't roll back S3 operation: {}", "Couldn't roll back S3 operation: {}",
e.to_string() e.message().unwrap_or_default()
)) ))
})?; })?;
@@ -180,7 +192,7 @@ pub async fn delete_file(
.map_err(|err| { .map_err(|err| {
ServerFnError::new(format!( ServerFnError::new(format!(
"Couldn't roll back Dynamo operation: {}", "Couldn't roll back Dynamo operation: {}",
err.to_string() err.message().unwrap_or_default()
)) ))
})?; })?;
@@ -230,5 +242,10 @@ pub async fn list_files(
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
}) })
.map_err(|e| ServerFnError::new(format!("Couldn't list files: {}", e.to_string()))) .map_err(|e| {
ServerFnError::new(format!(
"Couldn't list files: {}",
e.message().unwrap_or_default()
))
})
} }
+7 -1
View File
@@ -4,7 +4,7 @@ use leptos::prelude::*;
#[component] #[component]
pub fn StudentAcademicsPage() -> impl IntoView { pub fn StudentAcademicsPage() -> impl IntoView {
let controller = use_student_form("academics"); let controller = use_student_form("academics", true);
view! { view! {
<Suspense fallback=Loading> <Suspense fallback=Loading>
@@ -20,9 +20,11 @@ pub fn StudentAcademicsPage() -> impl IntoView {
title="Academic Information" title="Academic Information"
description="This information will allow you to be eligible for any scholarships that have specific academic/grade requirements." description="This information will allow you to be eligible for any scholarships that have specific academic/grade requirements."
on_submit=controller.submit_action on_submit=controller.submit_action
disabled=controller.submit_pending
> >
<OutlinedTextField <OutlinedTextField
label="Unweighted GPA:" label="Unweighted GPA:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="unweighted_gpa" data_member="unweighted_gpa"
placeholder="4.0" placeholder="4.0"
@@ -31,6 +33,7 @@ pub fn StudentAcademicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Weighted GPA:" label="Weighted GPA:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="weighted_gpa" data_member="weighted_gpa"
placeholder="4.5" placeholder="4.5"
@@ -39,6 +42,7 @@ pub fn StudentAcademicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Highest SAT Score:" label="Highest SAT Score:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="sat_score" data_member="sat_score"
placeholder="1600" placeholder="1600"
@@ -47,6 +51,7 @@ pub fn StudentAcademicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Highest ACT Score:" label="Highest ACT Score:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="act_score" data_member="act_score"
placeholder="36" placeholder="36"
@@ -55,6 +60,7 @@ pub fn StudentAcademicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Academic Honors:" label="Academic Honors:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="academic_honors" data_member="academic_honors"
placeholder="High Honors, etc." placeholder="High Honors, etc."
+4 -2
View File
@@ -5,7 +5,7 @@ use leptos::prelude::*;
#[component] #[component]
pub fn StudentAthleticsPage() -> impl IntoView { pub fn StudentAthleticsPage() -> impl IntoView {
let controller = use_student_form("athletics"); let controller = use_student_form("athletics", true);
view! { view! {
<Show when=move || controller.data_resource.get().is_some() fallback=Loading> <Show when=move || controller.data_resource.get().is_some() fallback=Loading>
@@ -15,6 +15,7 @@ pub fn StudentAthleticsPage() -> impl IntoView {
title="Athletics Information" title="Athletics Information"
description="This information will allow you to be eligible for any scholarships that have specific sports requirements." description="This information will allow you to be eligible for any scholarships that have specific sports requirements."
on_submit=controller.submit_action on_submit=controller.submit_action
disabled=controller.submit_pending
> >
<MultiEntry <MultiEntry
label="Athletics Activities:" label="Athletics Activities:"
@@ -39,7 +40,8 @@ pub fn StudentAthleticsPage() -> impl IntoView {
"Lacrosse", "Lacrosse",
"Softball", "Softball",
"Tennis", "Tennis",
"Baseball" "Baseball",
"Volleyball"
] ]
), ),
input!( input!(
+12 -2
View File
@@ -9,7 +9,7 @@ pub fn StudentDemographicsPage() -> impl IntoView {
// I'm going to test out creating this form without using a panel - we'll just use the page as // I'm going to test out creating this form without using a panel - we'll just use the page as
// it is with a specially spaced invisible div instead of a shaded container. // it is with a specially spaced invisible div instead of a shaded container.
let controller = use_student_form("demographics"); let controller = use_student_form("demographics", true);
view! { view! {
<Show when=move || controller.data_resource.get().is_some() fallback=Loading> <Show when=move || controller.data_resource.get().is_some() fallback=Loading>
@@ -19,9 +19,11 @@ pub fn StudentDemographicsPage() -> impl IntoView {
title="Student Demographic Form" title="Student Demographic Form"
description="Here you'll set up some basic information about yourself." description="Here you'll set up some basic information about yourself."
on_submit=controller.submit_action on_submit=controller.submit_action
disabled=controller.submit_pending
> >
<OutlinedTextField <OutlinedTextField
label="First Name:" label="First Name:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="first_name" data_member="first_name"
placeholder="John" placeholder="John"
@@ -30,6 +32,7 @@ pub fn StudentDemographicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Last Name:" label="Last Name:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="last_name" data_member="last_name"
placeholder="Smith" placeholder="Smith"
@@ -38,6 +41,7 @@ pub fn StudentDemographicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Date of Birth:" label="Date of Birth:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="dob" data_member="dob"
placeholder="MM/DD/YYYY" placeholder="MM/DD/YYYY"
@@ -46,8 +50,9 @@ pub fn StudentDemographicsPage() -> impl IntoView {
/> />
<Select <Select
label="Gender:" label="Gender:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="dob" data_member="gender"
required=true required=true
value_list=vec![ value_list=vec![
"Male".to_string(), "Male".to_string(),
@@ -57,6 +62,7 @@ pub fn StudentDemographicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Student ID Number:" label="Student ID Number:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="id_number" data_member="id_number"
placeholder="Your 7 digit number..." placeholder="Your 7 digit number..."
@@ -65,6 +71,7 @@ pub fn StudentDemographicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Preferred Email:" label="Preferred Email:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="email" data_member="email"
placeholder="me@example.com" placeholder="me@example.com"
@@ -73,6 +80,7 @@ pub fn StudentDemographicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Preferred Phone Number:" label="Preferred Phone Number:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="phone_number" data_member="phone_number"
placeholder="000-000-0000" placeholder="000-000-0000"
@@ -81,6 +89,7 @@ pub fn StudentDemographicsPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Street Address:" label="Street Address:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="street_address" data_member="street_address"
placeholder="234 Judd Rd" placeholder="234 Judd Rd"
@@ -89,6 +98,7 @@ pub fn StudentDemographicsPage() -> impl IntoView {
/> />
<Select <Select
label="Town:" label="Town:"
disabled=controller.submit_pending
value_list=vec!["Southbury".to_string(), "Middlebury".to_string()] value_list=vec!["Southbury".to_string(), "Middlebury".to_string()]
data_map=controller.data_map data_map=controller.data_map
data_member="town" data_member="town"
+4 -2
View File
@@ -5,20 +5,22 @@ use crate::pages::student::form_setup::use_student_form;
#[component] #[component]
pub fn StudentExtracurricularsPage() -> impl IntoView { pub fn StudentExtracurricularsPage() -> impl IntoView {
let controller = use_student_form("demographics"); let controller = use_student_form("demographics", true);
view! { view! {
<Show when=move || controller.data_resource.get().is_some() fallback=Loading> <Show when=move || controller.data_resource.get().is_some() fallback=Loading>
<div class="flex flex-1" /> <div class="flex flex-1" />
<div class="flex flex-col flex-2 mt-6"> <div class="flex flex-col flex-2 mt-6">
<ValidatedForm <ValidatedForm
title="Extracurricular Information" title="Extracurricular and Community Service Information"
description="This information will allow you to be eligible for any scholarships that have specific extracurricular requirements." description="This information will allow you to be eligible for any scholarships that have specific extracurricular requirements."
on_submit=controller.submit_action on_submit=controller.submit_action
disabled=controller.submit_pending
> >
// Inputs here. // Inputs here.
<OutlinedTextField <OutlinedTextField
label="Total number of service hours:" label="Total number of service hours:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="service_hours" data_member="service_hours"
placeholder="Any number..." placeholder="Any number..."
+12 -1
View File
@@ -4,7 +4,7 @@ use crate::pages::student::form_setup::use_student_form;
#[component] #[component]
pub fn StudentFamilyPage() -> impl IntoView { pub fn StudentFamilyPage() -> impl IntoView {
let controller = use_student_form("family"); let controller = use_student_form("family", true);
view! { view! {
<Show when=move || controller.data_resource.get().is_some() fallback=Loading> <Show when=move || controller.data_resource.get().is_some() fallback=Loading>
@@ -14,10 +14,12 @@ pub fn StudentFamilyPage() -> impl IntoView {
title="Family Information" title="Family Information"
description="General family information. You must fill out information about at least one parent." description="General family information. You must fill out information about at least one parent."
on_submit=controller.submit_action on_submit=controller.submit_action
disabled=controller.submit_pending
> >
// Inputs here. // Inputs here.
<OutlinedTextField <OutlinedTextField
label="Total number of children in family:" label="Total number of children in family:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="num_children" data_member="num_children"
placeholder="Any number..." placeholder="Any number..."
@@ -26,6 +28,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Total number of children currently attending college:" label="Total number of children currently attending college:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="num_children_college" data_member="num_children_college"
placeholder="Any number..." placeholder="Any number..."
@@ -34,6 +37,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Parent/Guardian 1 Name:" label="Parent/Guardian 1 Name:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="parent_one_name" data_member="parent_one_name"
placeholder="John Smith" placeholder="John Smith"
@@ -43,6 +47,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
// TODO Could this be a select dropdown? // TODO Could this be a select dropdown?
<OutlinedTextField <OutlinedTextField
label="Parent/Guardian 1 Relationship:" label="Parent/Guardian 1 Relationship:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="parent_one_relationship" data_member="parent_one_relationship"
placeholder="Mother/Father" placeholder="Mother/Father"
@@ -52,6 +57,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
// TODO Get a better example // TODO Get a better example
<OutlinedTextField <OutlinedTextField
label="Parent/Guardian 1 Occupation:" label="Parent/Guardian 1 Occupation:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="parent_one_occupation" data_member="parent_one_occupation"
placeholder="Milkman" placeholder="Milkman"
@@ -60,6 +66,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Parent/Guardian 1 Employer:" label="Parent/Guardian 1 Employer:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="parent_one_employer" data_member="parent_one_employer"
placeholder="Example Employer" placeholder="Example Employer"
@@ -69,6 +76,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
<OutlinedTextField <OutlinedTextField
label="Parent/Guardian 2 Name:" label="Parent/Guardian 2 Name:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="parent_two_name" data_member="parent_two_name"
placeholder="John Smith" placeholder="John Smith"
@@ -77,6 +85,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Parent/Guardian 2 Relationship:" label="Parent/Guardian 2 Relationship:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="parent_two_relationship" data_member="parent_two_relationship"
placeholder="Mother/Father" placeholder="Mother/Father"
@@ -85,6 +94,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Parent/Guardian 2 Occupation:" label="Parent/Guardian 2 Occupation:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="parent_two_occupation" data_member="parent_two_occupation"
placeholder="Milkman" placeholder="Milkman"
@@ -93,6 +103,7 @@ pub fn StudentFamilyPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="Parent/Guardian 2 Employer:" label="Parent/Guardian 2 Employer:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="parent_two_employer" data_member="parent_two_employer"
placeholder="Example Employer" placeholder="Example Employer"
+34 -2
View File
@@ -1,6 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use leptos::prelude::*; use leptos::prelude::*;
use uuid::Uuid;
use crate::common::ValueType; use crate::common::ValueType;
use crate::components::{Toast, ToastContext};
use crate::pages::api::students::{get_student_data, PutStudentData}; use crate::pages::api::students::{get_student_data, PutStudentData};
use crate::utils::get_user_claims; use crate::utils::get_user_claims;
@@ -9,11 +11,13 @@ pub struct StudentFormInfo {
pub user_id: Memo<Option<String>>, pub user_id: Memo<Option<String>>,
pub submit_action: Callback<()>, pub submit_action: Callback<()>,
pub refresh_trigger: Trigger, pub refresh_trigger: Trigger,
pub data_resource: Resource<Result<HashMap<String, ValueType>, ServerFnError>> pub data_resource: Resource<Result<HashMap<String, ValueType>, ServerFnError>>,
pub submit_pending: Memo<bool>,
} }
pub fn use_student_form( pub fn use_student_form(
form_type: impl Into<Signal<String>> form_type: impl Into<Signal<String>>,
enable_toasts: bool,
) -> StudentFormInfo { ) -> StudentFormInfo {
let form_type = form_type.into(); let form_type = form_type.into();
@@ -53,11 +57,39 @@ pub fn use_student_form(
}); });
}; };
if enable_toasts {
let mut toasts = expect_context::<ToastContext>();
Effect::watch(
move || submit_action.value().get(),
move |value, _, _| {
let Some(result) = value else {
return;
};
let toast = match result {
Ok(_) => Toast::new()
.id(Uuid::new_v4())
.header("Submission Successful")
.msg("You may continue editing or fill out another form."),
Err(e) => Toast::new()
.id(Uuid::new_v4())
.header("Submission Failed")
.msg(e.to_string())
};
toasts.toast(toast);
untrack(move || submit_action.clear());
},
false
);
}
StudentFormInfo { StudentFormInfo {
data_map, data_map,
refresh_trigger, refresh_trigger,
data_resource, data_resource,
user_id, user_id,
submit_action: on_submit.into(), submit_action: on_submit.into(),
submit_pending: submit_action.pending()
} }
} }
+1
View File
@@ -13,6 +13,7 @@ pub fn StudentHomePage() -> impl IntoView {
"ssmith@region15.org" "ssmith@region15.org"
</a>"." </a>"."
</div> </div>
<div class="text-lg">"The due date for this form is on April 2nd, 2026 at 2:05pm."</div>
<div class="text-lg">"Here are some links to get you started:"</div> <div class="text-lg">"Here are some links to get you started:"</div>
<DashboardButton <DashboardButton
title="Scholarship Book" title="Scholarship Book"
@@ -4,7 +4,7 @@ use leptos::prelude::*;
#[component] #[component]
pub fn StudentSpecificRequirementsPage() -> impl IntoView { pub fn StudentSpecificRequirementsPage() -> impl IntoView {
let controller = use_student_form("specifics"); let controller = use_student_form("specifics", true);
view! { view! {
<Suspense fallback=Loading> <Suspense fallback=Loading>
@@ -20,9 +20,11 @@ pub fn StudentSpecificRequirementsPage() -> impl IntoView {
title="Scholarship-Specific Eligibility Requirements" title="Scholarship-Specific Eligibility Requirements"
description="These questions are specific to some scholarships. Indicate yes or no for each question." description="These questions are specific to some scholarships. Indicate yes or no for each question."
on_submit=controller.submit_action on_submit=controller.submit_action
disabled=controller.submit_pending
> >
<RadioList <RadioList
label="Have you attended BAS?" label="Have you attended BAS?"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="attend_bas" data_member="attend_bas"
items=vec!["Yes".to_string(), "No".to_string()] items=vec!["Yes".to_string(), "No".to_string()]
@@ -30,6 +32,7 @@ pub fn StudentSpecificRequirementsPage() -> impl IntoView {
/> />
<RadioList <RadioList
label="Are you a member of Midd-South Catholic Church?" label="Are you a member of Midd-South Catholic Church?"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="middsouth_church" data_member="middsouth_church"
items=vec!["Yes".to_string(), "No".to_string()] items=vec!["Yes".to_string(), "No".to_string()]
@@ -37,6 +40,7 @@ pub fn StudentSpecificRequirementsPage() -> impl IntoView {
/> />
<RadioList <RadioList
label="Do you have a family member that is or has served in the US military? (Note: you may be asked for proof of service)" label="Do you have a family member that is or has served in the US military? (Note: you may be asked for proof of service)"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="family_military_service" data_member="family_military_service"
items=vec!["Yes".to_string(), "No".to_string()] items=vec!["Yes".to_string(), "No".to_string()]
@@ -44,6 +48,7 @@ pub fn StudentSpecificRequirementsPage() -> impl IntoView {
/> />
<RadioList <RadioList
label="Have you participated in Pomperaug Youth Baseball?" label="Have you participated in Pomperaug Youth Baseball?"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="youth_baseball" data_member="youth_baseball"
items=vec!["Yes".to_string(), "No".to_string()] items=vec!["Yes".to_string(), "No".to_string()]
@@ -51,6 +56,7 @@ pub fn StudentSpecificRequirementsPage() -> impl IntoView {
/> />
<RadioList <RadioList
label="Have you participated in the Panthers Aquatic Club?" label="Have you participated in the Panthers Aquatic Club?"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="aquatic_club" data_member="aquatic_club"
items=vec!["Yes".to_string(), "No".to_string()] items=vec!["Yes".to_string(), "No".to_string()]
@@ -58,6 +64,7 @@ pub fn StudentSpecificRequirementsPage() -> impl IntoView {
/> />
<RadioList <RadioList
label="Are at least one of your parents a member of the Region 15 PEA?" label="Are at least one of your parents a member of the Region 15 PEA?"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="pea_member" data_member="pea_member"
items=vec!["Yes".to_string(), "No".to_string()] items=vec!["Yes".to_string(), "No".to_string()]
@@ -65,6 +72,7 @@ pub fn StudentSpecificRequirementsPage() -> impl IntoView {
/> />
<RadioList <RadioList
label="Have you participated in the PHS Music Program (Band, Chorus, etc)?" label="Have you participated in the PHS Music Program (Band, Chorus, etc)?"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="music_program" data_member="music_program"
items=vec!["Yes".to_string(), "No".to_string()] items=vec!["Yes".to_string(), "No".to_string()]
+11 -2
View File
@@ -4,7 +4,7 @@ use crate::pages::student::form_setup::use_student_form;
#[component] #[component]
pub fn StudentUniversityPage() -> impl IntoView { pub fn StudentUniversityPage() -> impl IntoView {
let controller = use_student_form("university"); let controller = use_student_form("university", true);
// college name, state, city, zip // college name, state, city, zip
// major, study field, intended career, college_acceptance // major, study field, intended career, college_acceptance
@@ -17,10 +17,12 @@ pub fn StudentUniversityPage() -> impl IntoView {
title="University Information" title="University Information"
description="Here you may fill out general information about the university you plan to attend." description="Here you may fill out general information about the university you plan to attend."
on_submit=controller.submit_action on_submit=controller.submit_action
disabled=controller.submit_pending
> >
// Inputs here. // Inputs here.
<OutlinedTextField <OutlinedTextField
label="University Name:" label="University Name:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="college_name" data_member="college_name"
placeholder="University of Example" placeholder="University of Example"
@@ -29,6 +31,7 @@ pub fn StudentUniversityPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="University Street Address:" label="University Street Address:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="college_city" data_member="college_city"
placeholder="123 Example Rd" placeholder="123 Example Rd"
@@ -37,6 +40,7 @@ pub fn StudentUniversityPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="University State:" label="University State:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="college_state" data_member="college_state"
placeholder="CT, AZ, etc." placeholder="CT, AZ, etc."
@@ -45,6 +49,7 @@ pub fn StudentUniversityPage() -> impl IntoView {
/> />
<OutlinedTextField <OutlinedTextField
label="University ZIP:" label="University ZIP:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="college_zip" data_member="college_zip"
placeholder="12345" placeholder="12345"
@@ -53,13 +58,15 @@ pub fn StudentUniversityPage() -> impl IntoView {
/> />
<Select <Select
label="Have you been sent an acceptance to the university?" label="Have you been sent an acceptance to the university?"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="college_acceptance" data_member="college_acceptance"
required=true required=true
value_list=vec!["Yes".to_string(), "No".to_string()] value_list=vec!["Yes".to_string(), "No".to_string()]
/> />
<Select <Select
label="Chosen Major:" label="Intended Major/Field of Study:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="major" data_member="major"
required=true required=true
@@ -90,10 +97,12 @@ pub fn StudentUniversityPage() -> impl IntoView {
"Landscaping".to_string(), "Landscaping".to_string(),
"Legal Studies".to_string(), "Legal Studies".to_string(),
"Criminal Justice".to_string(), "Criminal Justice".to_string(),
"None of the above".to_string(),
] ]
/> />
<OutlinedTextField <OutlinedTextField
label="Intended Career:" label="Intended Career:"
disabled=controller.submit_pending
data_map=controller.data_map data_map=controller.data_map
data_member="intended_career" data_member="intended_career"
placeholder="Engineer, Artist, etc." placeholder="Engineer, Artist, etc."
+2 -1
View File
@@ -5,7 +5,7 @@ use leptos::prelude::*;
#[component] #[component]
pub fn StudentWorkExperiencePage() -> impl IntoView { pub fn StudentWorkExperiencePage() -> impl IntoView {
let controller = use_student_form("workexp"); let controller = use_student_form("workexp", true);
view! { view! {
<Show when=move || controller.data_resource.get().is_some() fallback=Loading> <Show when=move || controller.data_resource.get().is_some() fallback=Loading>
@@ -15,6 +15,7 @@ pub fn StudentWorkExperiencePage() -> impl IntoView {
title="Student Work Experience" title="Student Work Experience"
description="Here you may add information about your past/current workplace." description="Here you may add information about your past/current workplace."
on_submit=controller.submit_action on_submit=controller.submit_action
disabled=controller.submit_pending
> >
<MultiEntry <MultiEntry
label="Work Experience:" label="Work Experience:"