diff --git a/src/app.rs b/src/app.rs index 7a15fb8..c51ccdc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,12 @@ -use leptos::logging::log; +use crate::pages::{AboutPage, ComparisonTestPage, HomePage, LoanerPage, ProviderPortal, TestPage}; +use leptos::leptos_dom::log; use leptos::prelude::*; use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context}; use leptos_oidc::{Auth, AuthParameters, AuthSignal, Challenge}; -use leptos_router::{components::{Route, Router, Routes}, path}; -use crate::pages::{AboutPage, ComparisonTestPage, HomePage, LoanerPage, TestPage}; +use leptos_router::{ + components::{Route, Router, Routes}, + path, +}; pub fn shell(options: LeptosOptions) -> impl IntoView { view! { @@ -32,7 +35,7 @@ pub fn App() -> impl IntoView { // injects a stylesheet into the document // id=leptos means cargo-leptos will hot-reload this stylesheet - + // sets the document title @@ -44,7 +47,8 @@ pub fn App() -> impl IntoView { } fn use_origin() -> String { - #[cfg(target_arch = "wasm32")] { + #[cfg(target_arch = "wasm32")] + { use leptos::web_sys; // Just get the current URL origin. web_sys::window() @@ -53,8 +57,9 @@ fn use_origin() -> String { .origin() .unwrap_or("http://localhost:3000/".to_string()) } - - #[cfg(not(target_arch = "wasm32"))] { + + #[cfg(not(target_arch = "wasm32"))] + { // Read the expected origin out of an environment variable. std::env::var("LP_SITE_ORIGIN").unwrap_or("http://localhost:3000/".to_string()) } @@ -63,7 +68,7 @@ fn use_origin() -> String { #[component] pub fn AppWithRoutes() -> impl IntoView { provide_meta_context(); - + let current_origin = use_origin(); log!("Current origin: {}", current_origin); @@ -82,7 +87,7 @@ pub fn AppWithRoutes() -> impl IntoView { let auth = Auth::signal(); provide_context(auth); // allows use of this signal in lower areas of the tree without - // explicitly passing it through the html tree + // explicitly passing it through the html tree let _ = Auth::init(parameters); @@ -96,7 +101,7 @@ pub fn AppWithRoutes() -> impl IntoView { // sets the document title <Title text="R15 Scholarship App DEV" /> - + <main> // TODO Create a 404 page <Routes fallback=|| "Page not found.".into_view()> @@ -104,6 +109,7 @@ pub fn AppWithRoutes() -> impl IntoView { <Route path=path!("about") view=AboutPage/> <Route path=path!("test_page") view=TestPage/> <Route path=path!("comparison") view=ComparisonTestPage /> + <Route path=path!("providers") view=ProviderPortal /> <Route path=path!("loaners") view=LoanerPage /> <Route path=path!("loaners/:form_name") view=LoanerPage /> </Routes> diff --git a/src/common/comparison/base.rs b/src/common/comparison/base.rs index ec45f6c..aa6aea8 100644 --- a/src/common/comparison/base.rs +++ b/src/common/comparison/base.rs @@ -40,10 +40,15 @@ impl ComparisonData { display_text: display_text.into(), } } - + pub fn compare(&self, student_data: &ExpandableInfo) -> Result<bool, String> { - let value = student_data.data.get(&self.member) - .expect(format!("Couldn't find member {:?}, current student data: {:?}", self.member, student_data).as_str()); + let value = student_data.data.get(&self.member).expect( + format!( + "Couldn't find member {:?}, current student data: {:?}", + self.member, student_data + ) + .as_str(), + ); self.comparison.evaluate(value, &self.target_value) } } @@ -73,13 +78,13 @@ impl Comparison for ComparisonType { #[cfg(test)] mod tests { - use crate::common::comparison::test_utils::create_student_data; use super::*; - + use crate::common::comparison::test_utils::create_student_data; + #[test] fn test_student_data() { let student_data = create_student_data(); - + // Should succeed every time. let text_comp_success = ComparisonData { id: "first_name_success".to_string(), @@ -99,11 +104,11 @@ mod tests { category: "Basic Checks".to_string(), display_text: "First Name is Jane".to_string(), }; - + let mut result = text_comp_success.compare(&student_data); assert!(result.is_ok()); assert_eq!(result.unwrap(), true); - + result = text_comp_fail.compare(&student_data); assert!(result.is_err()); } diff --git a/src/common/comparison/lists.rs b/src/common/comparison/lists.rs index ff6112c..7dc3c53 100644 --- a/src/common/comparison/lists.rs +++ b/src/common/comparison/lists.rs @@ -62,7 +62,7 @@ impl Comparison for NumberListComparison { /// Represents all available comparisons for a text list. All comparisons are comparable to a /// single value. -/// +/// /// For example, using `TextListComparison::Contains` checks if a specific value is contained /// in the list. #[derive(Serialize, Deserialize, Debug, Clone)] @@ -148,12 +148,12 @@ impl Comparison for TextListComparison { /// For right now, nested lists should be flattened into a list of a single type before they /// are compared. The plan is to create a more robust framework that eventually be able to /// query specific lists and perform comparisons on them. -/// +/// /// For example: /// ``` /// NestedListComparison::FlattenToTextList(Box::new(TextListComparison::Contains)); /// ``` -/// +/// /// This will flatten a nested list into a single list of `ValueType::String` enums and then /// will check if the list contains the indicated target value. #[derive(Serialize, Deserialize, Debug, Clone)] @@ -288,10 +288,12 @@ impl Comparison for MapListComparison { #[cfg(test)] mod tests { + use super::super::test_utils::create_map_list_helper; use super::*; - use super::super::test_utils::{create_map_list_helper}; - use crate::common::{ComparisonType}; - use crate::common::comparison::test_utils::{create_empty_lists, create_number_list, create_text_list}; + use crate::common::ComparisonType; + use crate::common::comparison::test_utils::{ + create_empty_lists, create_number_list, create_text_list, + }; #[test] fn map_list_flatten_to_text_list() { @@ -415,61 +417,59 @@ mod tests { assert_eq!(incorrect_value.unwrap(), false); assert_eq!(mixed_incorrect_type.unwrap(), false); } - + #[test] fn text_list_contains() { let (none_list, empty_list) = create_empty_lists(); let num_list = create_number_list(); let text_list = create_text_list(); - + // Define comparisons here let comparison = ComparisonType::TextList(TextListComparison::Contains); let target_value = ValueType::String(Some("one".to_string())); - + let none_result = comparison.evaluate(&none_list, &target_value); let empty_result = comparison.evaluate(&empty_list, &target_value); let num_result = comparison.evaluate(&num_list, &target_value); let text_list = comparison.evaluate(&text_list, &target_value); - + assert_eq!(none_result.unwrap(), false); assert_eq!(empty_result.unwrap(), false); assert_eq!(num_result.unwrap(), false); assert_eq!(text_list.unwrap(), true); } - + #[test] fn text_list_empty() { let none_list = ValueType::List(None); let empty_list = ValueType::List(Some(vec![])); - let long_list = ValueType::List(Some(vec![ - ValueType::String(None), - ])); - + let long_list = ValueType::List(Some(vec![ValueType::String(None)])); + let empty_comp = ComparisonType::TextList(TextListComparison::IsEmpty); let not_empty_comp = ComparisonType::TextList(TextListComparison::IsNotEmpty); - + // This comparison does not use a target value, as it should just check if the list // either doesn't exist or is empty. let mut result = empty_comp.evaluate(&empty_list, &ValueType::List(None)); assert!(result.is_ok()); assert_eq!(result.unwrap(), true); - + result = empty_comp.evaluate(&none_list, &ValueType::List(None)); assert!(result.is_ok()); assert_eq!(result.unwrap(), true); - + result = empty_comp.evaluate(&long_list, &ValueType::List(None)); assert!(result.is_ok()); assert_eq!(result.unwrap(), false); - + result = not_empty_comp.evaluate(&empty_list, &ValueType::List(None)); assert!(result.is_ok()); assert_eq!(result.unwrap(), false); - + result = not_empty_comp.evaluate(&none_list, &ValueType::List(None)); assert!(result.is_ok()); assert_eq!(result.unwrap(), false); - + result = not_empty_comp.evaluate(&long_list, &ValueType::List(None)); assert!(result.is_ok()); assert_eq!(result.unwrap(), true); diff --git a/src/common/comparison/mod.rs b/src/common/comparison/mod.rs index 1f7ad07..bcf85b1 100644 --- a/src/common/comparison/mod.rs +++ b/src/common/comparison/mod.rs @@ -9,8 +9,8 @@ pub use self::{base::*, lists::*, number::*, text::*, traits::*}; #[cfg(test)] mod test_utils { - use std::collections::HashMap; use crate::common::{ExpandableInfo, ValueType}; + use std::collections::HashMap; pub fn create_student_data() -> ExpandableInfo { let map = create_map_helper( @@ -40,19 +40,16 @@ mod test_utils { ValueType::String(Some(10.to_string())), ])), ValueType::String(Some("N/A".to_string())), - ] + ], ))), ValueType::Map(Some(create_map_helper( - vec![ - "activity_name".to_string(), - "total_hours".to_string(), - ], + vec!["activity_name".to_string(), "total_hours".to_string()], vec![ ValueType::String(Some("Raking".to_string())), ValueType::Number(Some(30.to_string())), - ] + ], ))), - ] + ], ); let mut info = ExpandableInfo::new("test"); @@ -61,9 +58,11 @@ mod test_utils { info } - pub fn create_map_helper(keys: Vec<String>, values: Vec<ValueType>) -> HashMap<String, ValueType> { - keys - .iter() + pub fn create_map_helper( + keys: Vec<String>, + values: Vec<ValueType>, + ) -> HashMap<String, ValueType> { + keys.iter() .cloned() .zip(values.iter().cloned()) .collect::<HashMap<String, ValueType>>() @@ -76,7 +75,7 @@ mod test_utils { "last_name".to_string(), "gender".to_string(), "sat_score".to_string(), - "grades_participated".to_string() + "grades_participated".to_string(), ], vec![ ValueType::String(Some("John".to_string())), @@ -85,8 +84,8 @@ mod test_utils { ValueType::Number(Some(1200.to_string())), ValueType::List(Some(vec![ ValueType::String(Some("9".to_string())), - ValueType::String(Some("10".to_string())) - ])) + ValueType::String(Some("10".to_string())), + ])), ], ); @@ -96,7 +95,7 @@ mod test_utils { "last_name".to_string(), "gender".to_string(), "sat_score".to_string(), - "grades_participated".to_string() + "grades_participated".to_string(), ], vec![ ValueType::String(Some("Jane".to_string())), @@ -105,8 +104,8 @@ mod test_utils { ValueType::Number(Some(1000.to_string())), ValueType::List(Some(vec![ ValueType::String(Some("11".to_string())), - ValueType::String(Some("12".to_string())) - ])) + ValueType::String(Some("12".to_string())), + ])), ], ); @@ -115,14 +114,14 @@ mod test_utils { ValueType::Map(Some(map_two)), ])) } - + pub fn create_empty_lists() -> (ValueType, ValueType) { let none_list = ValueType::List(None); let empty_list = ValueType::List(Some(vec![])); - + (none_list, empty_list) } - + pub fn create_number_list() -> ValueType { ValueType::List(Some(vec![ ValueType::Number(Some(1.to_string())), @@ -132,7 +131,7 @@ mod test_utils { ValueType::Number(Some(5.to_string())), ])) } - + pub fn create_text_list() -> ValueType { ValueType::List(Some(vec![ ValueType::String(Some("one".to_string())), diff --git a/src/common/mod.rs b/src/common/mod.rs index 3dd4824..135600c 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,8 +1,10 @@ mod comparison; mod expandable_info; +mod form_params; mod form_schema; mod user; mod value_type; -mod form_params; -pub use self::{comparison::*, expandable_info::*, form_schema::*, user::*, value_type::*, form_params::*}; +pub use self::{ + comparison::*, expandable_info::*, form_params::*, form_schema::*, user::*, value_type::*, +}; diff --git a/src/components/multi_entry.rs b/src/components/multi_entry.rs index 78c8e44..8d7aca3 100644 --- a/src/components/multi_entry.rs +++ b/src/components/multi_entry.rs @@ -74,7 +74,8 @@ pub fn MultiEntry( #[prop()] data_map: RwSignal<HashMap<String, ValueType>>, /// Currently unused. - #[prop(optional, into)] name_member: String, + #[prop(optional, into)] + name_member: String, /// The type and order of the inputs for each `Entry` component within this `MultiEntry` component. #[prop(optional)] schema: Vec<InputType>, diff --git a/src/pages/api/comparisons.rs b/src/pages/api/comparisons.rs index be8469c..b343b24 100644 --- a/src/pages/api/comparisons.rs +++ b/src/pages/api/comparisons.rs @@ -2,189 +2,190 @@ use crate::pages::utils::server_utils::create_dynamo_client; #[cfg(feature = "ssr")] -use aws_sdk_dynamodb::{ - error::ProvideErrorMetadata, -}; +use aws_sdk_dynamodb::error::ProvideErrorMetadata; +use crate::common::{ + ComparisonData, ComparisonType, MapListComparison, NumberComparison, NumberListComparison, + TextComparison, TextListComparison, ValueType, +}; use leptos::leptos_dom::log; use leptos::prelude::ServerFnError; use leptos::server; -use crate::common::{ComparisonData, ComparisonType, MapListComparison, NumberComparison, NumberListComparison, TextComparison, TextListComparison, ValueType}; /// 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. fn create_sports_comparisons() -> Vec<ComparisonData> { - vec! [ + vec![ ComparisonData::new( "sports_football", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Football".to_string())), "Sports Participation", - "Football" + "Football", ), ComparisonData::new( "sports_soccer", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Soccer".to_string())), "Sports Participation", - "Soccer" + "Soccer", ), ComparisonData::new( "sports_cheerleading", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Cheerleading".to_string())), "Sports Participation", - "Cheerleading" + "Cheerleading", ), ComparisonData::new( "sports_field_hockey", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Field Hockey".to_string())), "Sports Participation", - "Field Hockey" + "Field Hockey", ), ComparisonData::new( "sports_swimming", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Swimming".to_string())), "Sports Participation", - "Swimming" + "Swimming", ), ComparisonData::new( "sports_golf", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Golf".to_string())), "Sports Participation", - "Golf" + "Golf", ), ComparisonData::new( "sports_basketball", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Basketball".to_string())), "Sports Participation", - "Basketball" + "Basketball", ), ComparisonData::new( "sports_track", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Track".to_string())), "Sports Participation", - "Track" + "Track", ), ComparisonData::new( "sports_gymnastics", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Gymnastics".to_string())), "Sports Participation", - "Gymnastics" + "Gymnastics", ), ComparisonData::new( "sports_ice_hockey", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Ice Hockey".to_string())), "Sports Participation", - "Ice Hockey" + "Ice Hockey", ), ComparisonData::new( "sports_ski", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Ski".to_string())), "Sports Participation", - "Ski" + "Ski", ), ComparisonData::new( "sports_wrestling", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Wrestling".to_string())), "Sports Participation", - "Wrestling" + "Wrestling", ), ComparisonData::new( "sports_lacrosse", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Lacrosse".to_string())), "Sports Participation", - "Lacrosse" + "Lacrosse", ), ComparisonData::new( "sports_softball", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Softball".to_string())), "Sports Participation", - "Softball" + "Softball", ), ComparisonData::new( "sports_tennis", "sports_participation", ComparisonType::MapList(MapListComparison::FlattenToTextList( "sport_name".to_string(), - Box::new(TextListComparison::Contains) + Box::new(TextListComparison::Contains), )), ValueType::String(Some("Tennis".to_string())), "Sports Participation", - "Tennis" - ) + "Tennis", + ), ] } #[server(CreateTestComparisons, endpoint = "/comparisons/create-test")] -pub async fn create_test_comparisons() -> Result<(), ServerFnError> { +pub async fn create_test_comparisons() -> Result<(), ServerFnError> { let client = create_dynamo_client().await; log!("Creating test comparisons"); @@ -195,96 +196,96 @@ pub async fn create_test_comparisons() -> Result<(), ServerFnError> { ComparisonType::Number(NumberComparison::GreaterThanOrEqual), ValueType::Number(Some(650.to_string())), "Academic Information", - "Math SAT Score > 650" + "Math SAT Score > 650", ); - + let service_hours_25 = ComparisonData::new( "map_sum_comp", "community_involvement", ComparisonType::MapList(MapListComparison::FlattenToNumberList( "service_hours".to_string(), - Box::new(NumberListComparison::Sum( - Box::new(NumberComparison::GreaterThanOrEqual) - )) + Box::new(NumberListComparison::Sum(Box::new( + NumberComparison::GreaterThanOrEqual, + ))), )), ValueType::Number(Some(25.to_string())), "Community Involvement", - "25 service hours" + "25 service hours", ); - + let service_hours_20 = ComparisonData::new( "service_hours_20", "community_involvement", ComparisonType::MapList(MapListComparison::FlattenToNumberList( "service_hours".to_string(), - Box::new(NumberListComparison::Sum( - Box::new(NumberComparison::GreaterThanOrEqual) - )) + Box::new(NumberListComparison::Sum(Box::new( + NumberComparison::GreaterThanOrEqual, + ))), )), ValueType::Number(Some(20.to_string())), "Community Involvement", - "20 service hours" + "20 service hours", ); - + let service_hours_30 = ComparisonData::new( "service_hours_30", "community_involvement", ComparisonType::MapList(MapListComparison::FlattenToNumberList( "service_hours".to_string(), - Box::new(NumberListComparison::Sum( - Box::new(NumberComparison::GreaterThanOrEqual) - )) + Box::new(NumberListComparison::Sum(Box::new( + NumberComparison::GreaterThanOrEqual, + ))), )), ValueType::Number(Some(30.to_string())), "Community Involvement", - "30 service hours" + "30 service hours", ); - + let residency_southbury = ComparisonData::new( "residency_southbury", "town", ComparisonType::Text(TextComparison::Matches), ValueType::String(Some("Southbury".to_string())), "Residency", - "Southbury" + "Southbury", ); - + let residency_middlebury = ComparisonData::new( "residency_middlebury", "town", ComparisonType::Text(TextComparison::Matches), ValueType::String(Some("Middlebury".to_string())), "Residency", - "Middlebury" + "Middlebury", ); - + let attended_bas = ComparisonData::new( "attended_bas", "attended_bas", ComparisonType::Text(TextComparison::Matches), ValueType::String(Some("Yes".to_string())), "Miscellaneous", - "Attended BAS" + "Attended BAS", ); - + let midd_south = ComparisonData::new( "midd_south", "middsouth_church", ComparisonType::Text(TextComparison::Matches), ValueType::String(Some("Yes".to_string())), "Miscellaneous", - "Member of Midd-South Church" + "Member of Midd-South Church", ); - + let family_military = ComparisonData::new( "family_military", "family_military_service", ComparisonType::Text(TextComparison::Matches), ValueType::String(Some("Yes".to_string())), "Miscellaneous", - "Family with Military Service" + "Family with Military Service", ); - + let mut sports_comps = create_sports_comparisons(); let mut major_comps = Vec::<ComparisonData>::new(); @@ -299,7 +300,7 @@ pub async fn create_test_comparisons() -> Result<(), ServerFnError> { midd_south, family_military, ]; - + comp_list.append(&mut sports_comps); comp_list.append(&mut major_comps); @@ -309,13 +310,13 @@ pub async fn create_test_comparisons() -> Result<(), ServerFnError> { .table_name("leptos-comparison-test") .set_item(Some(serde_dynamo::to_item(&comparison)?)) .send() - .await + .await { let msg = err.message().unwrap_or("An unknown error occurred"); - return Err(ServerFnError::new(msg)) + return Err(ServerFnError::new(msg)); } } - + Ok(()) } @@ -340,7 +341,7 @@ pub async fn get_comparison_info() -> Result<Vec<ComparisonData>, ServerFnError> } else { Ok(vec![]) } - }, + } Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred"); Err(ServerFnError::new(msg)) diff --git a/src/pages/api/mod.rs b/src/pages/api/mod.rs index 810edc2..e13ff2b 100644 --- a/src/pages/api/mod.rs +++ b/src/pages/api/mod.rs @@ -1,4 +1,4 @@ mod comparisons; mod scholarships; -pub use self::{comparisons::*, scholarships::*}; \ No newline at end of file +pub use self::{comparisons::*, scholarships::*}; diff --git a/src/pages/api/scholarships.rs b/src/pages/api/scholarships.rs index edc7b4c..4ec5301 100644 --- a/src/pages/api/scholarships.rs +++ b/src/pages/api/scholarships.rs @@ -1,13 +1,10 @@ #[cfg(feature = "ssr")] -use aws_sdk_dynamodb::{ - types::AttributeValue, - error::ProvideErrorMetadata, -}; +use aws_sdk_dynamodb::{error::ProvideErrorMetadata, types::AttributeValue}; +use crate::common::ExpandableInfo; use leptos::leptos_dom::log; use leptos::prelude::ServerFnError; use leptos::server; -use crate::common::ExpandableInfo; #[cfg(feature = "ssr")] use crate::pages::utils::server_utils::create_dynamo_client; @@ -49,7 +46,10 @@ pub async fn create_scholarship_info(info: ExpandableInfo) -> Result<(), ServerF let client = create_dynamo_client().await; - log!("Creating or updating scholarship with ID {:?}", info.subject); + log!( + "Creating or updating scholarship with ID {:?}", + info.subject + ); match client .put_item() @@ -82,7 +82,7 @@ pub async fn get_all_scholarship_info() -> Result<Vec<ExpandableInfo>, ServerFnE } else { Ok(Vec::new()) } - }, + } Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred"); Err(ServerFnError::new(msg)) diff --git a/src/pages/comparison_test.rs b/src/pages/comparison_test.rs index c08f9f4..4ef31cb 100644 --- a/src/pages/comparison_test.rs +++ b/src/pages/comparison_test.rs @@ -1,15 +1,13 @@ -use leptos::either::Either; +use super::UnauthenticatedPage; +use super::api::{get_all_scholarship_info, get_comparison_info}; use super::home_page::get_submission; -use super::{UnauthenticatedPage}; -use crate::common::{ - ComparisonData, ExpandableInfo, UserClaims, ValueType, -}; +use crate::common::{ComparisonData, ExpandableInfo, UserClaims, ValueType}; use crate::components::{ActionButton, Loading, Panel, Row}; +use leptos::either::Either; use leptos::leptos_dom::logging::console_log; use leptos::logging::log; use leptos::prelude::*; use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated}; -use super::api::{get_comparison_info, get_all_scholarship_info}; #[component] pub fn ComparisonTestPage() -> impl IntoView { @@ -28,17 +26,15 @@ pub fn ComparisonTestPage() -> impl IntoView { // we don't lose access to it in the future, should we need it again. let student_resource: Resource<ExpandableInfo> = Resource::new( move || user_claims.get().map(|claim| claim.claims.subject.clone()), - async - | opt_username - | match opt_username { - Some(subject) => get_submission(subject.clone()).await.unwrap_or_else(|e| { - console_log(e.to_string().as_str()); - ExpandableInfo::new(subject) - }), - None => ExpandableInfo::new("".to_owned()), - }, + async |opt_username| match opt_username { + Some(subject) => get_submission(subject.clone()).await.unwrap_or_else(|e| { + console_log(e.to_string().as_str()); + ExpandableInfo::new(subject) + }), + None => ExpandableInfo::new("".to_owned()), + }, ); - + let scholarship_resource: Resource<Vec<ExpandableInfo>> = Resource::new( move || Option::<String>::None, async |_| { @@ -46,9 +42,9 @@ pub fn ComparisonTestPage() -> impl IntoView { log!("An error occurred: {:?}", e); Vec::new() }) - } + }, ); - + let comparison_resource: Resource<Vec<ComparisonData>> = Resource::new( move || Option::<String>::None, async |_| { @@ -56,7 +52,7 @@ pub fn ComparisonTestPage() -> impl IntoView { log!("An error occurred: {:?}", e); Vec::new() }) - } + }, ); view! { @@ -89,9 +85,9 @@ pub fn ComparisonTestPage() -> impl IntoView { return Ok(Some(scholarship)) // students will always qualify when there are no requirements } }; - + let mut final_result = Ok(true); - + // The list should exist, and it should contain a series of strings. for val in list { let comp_opt = match val { @@ -101,7 +97,7 @@ pub fn ComparisonTestPage() -> impl IntoView { } _ => None }; - + let comparison_outcome = match comp_opt { Some(comp) => { // Perform the comparison @@ -109,7 +105,7 @@ pub fn ComparisonTestPage() -> impl IntoView { } _ => Err("".to_string()) }; - + match comparison_outcome { Ok(true) => {}, Ok(false) => final_result = Ok(false), @@ -119,14 +115,14 @@ pub fn ComparisonTestPage() -> impl IntoView { } } } - + match final_result { Ok(true) => Ok(Some(scholarship)), Ok(false) => Ok(None), Err(e) => Err(e) } }).collect::<Vec<Result<Option<&ExpandableInfo>, String>>>(); - + // Build a display string let mut strings = Vec::new(); strings.push("Scholarship results: \n".to_string()); @@ -143,9 +139,9 @@ pub fn ComparisonTestPage() -> impl IntoView { Err(msg) => strings.push(msg) } } - + let display_text = strings.join("\n").to_string(); - + result_msg.set(display_text); }>"Test Comparison"</ActionButton> </Row> diff --git a/src/pages/home_page.rs b/src/pages/home_page.rs index 4c6ddb9..c3ca850 100644 --- a/src/pages/home_page.rs +++ b/src/pages/home_page.rs @@ -5,24 +5,24 @@ use aws_sdk_dynamodb::{Client, error::ProvideErrorMetadata, types::AttributeValu #[cfg(feature = "ssr")] use serde_dynamo::{from_item, to_item}; #[cfg(feature = "ssr")] -use std::process::{Command, Stdio}; -#[cfg(feature = "ssr")] use std::io::Write; +#[cfg(feature = "ssr")] +use std::process::{Command, Stdio}; use crate::common::{ExpandableInfo, UserClaims}; -use crate::pages::UnauthenticatedPage; use crate::components::{ ActionButton, CheckboxList, Loading, MultiEntry, OutlinedTextField, Panel, RadioList, Row, - Select + Select, }; use crate::input; +use crate::pages::UnauthenticatedPage; +use base64::Engine; use leptos::leptos_dom::logging::console_log; use leptos::prelude::*; -use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated}; -use base64::Engine; use leptos::task::spawn_local; -use leptos::web_sys::HtmlAnchorElement; use leptos::wasm_bindgen::JsCast; +use leptos::web_sys::HtmlAnchorElement; +use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated}; use traits::{AsReactive, ReactiveCapture}; /// # Get Student Info @@ -125,16 +125,14 @@ pub async fn create_example_pdf() -> Result<Vec<u8>, ServerFnError> { let mut command = Command::new("typst"); command .arg("compile") - .arg("-") // Use stdin as input - .arg("-") // Write output to stdout + .arg("-") // Use stdin as input + .arg("-") // Write output to stdout .stdin(Stdio::piped()) .stdout(Stdio::piped()); let mut process = command.spawn()?; - process.stdin - .take().unwrap() - .write(template.as_bytes())?; + process.stdin.take().unwrap().write(template.as_bytes())?; let output = process.wait_with_output()?; let pdf_bytes = output.stdout; diff --git a/src/pages/loaner_page.rs b/src/pages/loaner_page.rs index 9595913..b4acf04 100644 --- a/src/pages/loaner_page.rs +++ b/src/pages/loaner_page.rs @@ -4,23 +4,25 @@ use aws_sdk_dynamodb::{Client, error::ProvideErrorMetadata, types::AttributeValu #[cfg(feature = "ssr")] use serde_dynamo::{from_item, to_item}; -use leptos::either::{Either, EitherOf3}; -use leptos::prelude::*; -use serde::{Deserialize, Serialize}; -use leptos::logging::log; -use leptos_router::params::Params; -use leptos_router::hooks::{use_navigate, use_params}; -use leptos::Params; -use traits::{AsReactive, ReactiveCapture}; use crate::common::{ExpandableInfo, ValueType}; -use crate::components::{ActionButton, Banner, DashboardButton, OutlinedTextField, Panel, Row, Select}; -use std::collections::HashMap; +use crate::components::{ + ActionButton, Banner, DashboardButton, OutlinedTextField, Panel, Row, Select, +}; use chrono::{FixedOffset, TimeZone}; +use leptos::Params; +use leptos::either::{Either, EitherOf3}; use leptos::html::Dialog; +use leptos::logging::log; +use leptos::prelude::*; +use leptos_router::hooks::{use_navigate, use_params}; +use leptos_router::params::Params; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use traits::{AsReactive, ReactiveCapture}; #[derive(Params, PartialEq)] struct LoanerParams { - form_name: Option<String> + form_name: Option<String>, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -39,7 +41,8 @@ struct LoanerReturnOutput { async fn get_loaners_return_list() -> Result<Vec<LoanerReturnOutput>, ServerFnError> { // We want to query the database for all loaners, but we only want the name and // date of borrowing. This will be cheaper than querying the entire table. - let dbclient = Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); + let dbclient = + Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); log!("Getting loaners from database."); @@ -48,34 +51,57 @@ async fn get_loaners_return_list() -> Result<Vec<LoanerReturnOutput>, ServerFnEr .table_name("loaner-info") .projection_expression("subject, first_name, last_name, date_taken") .send() - .await { + .await + { Ok(output) => { if let Some(items) = output.items { - Ok(items.into_iter().map(|item| { - let expandable: ExpandableInfo = from_item(item).unwrap(); - // This set of function calls is ridiculous, we need to make a better way to do this. - LoanerReturnOutput { - first_name: expandable.data.get("first_name").unwrap().as_string().unwrap_or_default().unwrap_or_default(), - last_name: expandable.data.get("last_name").unwrap().as_string().unwrap_or_default().unwrap_or_default(), - date: expandable.data.get("date_taken").unwrap().as_string().unwrap_or_default().unwrap_or_default(), - subject: expandable.subject - } - }).collect()) + Ok(items + .into_iter() + .map(|item| { + let expandable: ExpandableInfo = from_item(item).unwrap(); + // This set of function calls is ridiculous, we need to make a better way to do this. + LoanerReturnOutput { + first_name: expandable + .data + .get("first_name") + .unwrap() + .as_string() + .unwrap_or_default() + .unwrap_or_default(), + last_name: expandable + .data + .get("last_name") + .unwrap() + .as_string() + .unwrap_or_default() + .unwrap_or_default(), + date: expandable + .data + .get("date_taken") + .unwrap() + .as_string() + .unwrap_or_default() + .unwrap_or_default(), + subject: expandable.subject, + } + }) + .collect()) } else { Ok(vec![]) } - }, + } Err(err) => { log!("{:?}", err); let msg = err.message().unwrap_or("Unknown error"); Err(ServerFnError::new(msg)) - }, + } } } #[server] async fn check_in_loaner(student: LoanerReturnOutput) -> Result<(), ServerFnError> { - let dbclient = Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); + let dbclient = + Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); match dbclient .delete_item() @@ -96,13 +122,17 @@ async fn check_in_loaner(student: LoanerReturnOutput) -> Result<(), ServerFnErro #[server] async fn create_borrow_entry(input: ExpandableInfo) -> Result<(), ServerFnError> { let mut input = input; - let dbclient = Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); + let dbclient = + Client::new(&aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await); let current_time = chrono::Utc::now().format("%H:%M, %m/%d/%Y").to_string(); log!("Creating entry with timestamp {}", current_time); // Add the timestamp to the data map as a string and then push it to the database. - input.data.insert("date_taken".to_string(), ValueType::String(Some(current_time.clone()))); + input.data.insert( + "date_taken".to_string(), + ValueType::String(Some(current_time.clone())), + ); loop { let item: serde_dynamo::Item = to_item(&input)?; @@ -122,8 +152,8 @@ async fn create_borrow_entry(input: ExpandableInfo) -> Result<(), ServerFnError> // Retry database writes until we have a good subject ID. log!("Failed conditional check, forcing new subject ID."); input.subject = uuid::Uuid::new_v4().to_string(); - continue - }, + continue; + } _ => { let msg = err.message().unwrap_or("An unknown error occurred."); return Err(ServerFnError::new(msg)); @@ -165,19 +195,18 @@ pub fn LoanerPage() -> impl IntoView { // Check URL params. Depending on what string is visible here, we'll display a different form. let params = use_params::<LoanerParams>(); let form_passed = move || { - params.read() + params + .read() .as_ref() .ok() .and_then(|params| params.form_name.clone()) .unwrap_or_default() }; - - let form_view = move || { - match form_passed().as_str() { - "borrowing" => EitherOf3::A(LoanerBorrowForm), - "returning" => EitherOf3::B(LoanerReturnForm), - _ => EitherOf3::C("Choose something from the left.".into_view()) - } + + let form_view = move || match form_passed().as_str() { + "borrowing" => EitherOf3::A(LoanerBorrowForm), + "returning" => EitherOf3::B(LoanerReturnForm), + _ => EitherOf3::C("Choose something from the left.".into_view()), }; // We'll create the page here - the sidebar contains all the buttons, while the form @@ -221,21 +250,21 @@ pub fn LoanerBorrowForm() -> impl IntoView { let temp = ExpandableInfo::new(uuid::Uuid::new_v4().to_string()); let temp_reactive = temp.as_reactive(); let elements_disabled = RwSignal::new(false); - + view! { <div class="flex flex-col justify-center gap-2 mb-1 py-3 px-2"> <h2 class="text-2xl font-bold">"Borrower Information"</h2> <p class="text-lg">"Please fill out the form below."</p> </div> <Row> - <OutlinedTextField + <OutlinedTextField label="First Name:" placeholder="John" disabled=elements_disabled data_member="first_name" data_map=temp_reactive.data /> - <OutlinedTextField + <OutlinedTextField label="Last Name:" placeholder="Smith" disabled=elements_disabled @@ -244,7 +273,7 @@ pub fn LoanerBorrowForm() -> impl IntoView { /> </Row> <Row> - <OutlinedTextField + <OutlinedTextField label="Region 15 Email:" placeholder="example@region15.org" disabled=elements_disabled @@ -268,7 +297,7 @@ pub fn LoanerBorrowForm() -> impl IntoView { /> </Row> <Row> - <Select + <Select label="Loan Taken:" value_list=vec![ "Chromebook".to_string(), @@ -305,10 +334,12 @@ pub fn LoanerReturnForm() -> impl IntoView { let return_list_refresh = RwSignal::new(0); let return_list_resource: Resource<Vec<LoanerReturnOutput>> = Resource::new( move || return_list_refresh.get(), - async |_| get_loaners_return_list().await.unwrap_or_else(|e| { - log!("There was an error getting the loaner list: {}", e); - vec![] - }) + async |_| { + get_loaners_return_list().await.unwrap_or_else(|e| { + log!("There was an error getting the loaner list: {}", e); + vec![] + }) + }, ); let dialog_ref = NodeRef::<Dialog>::new(); @@ -340,7 +371,9 @@ pub fn LoanerReturnForm() -> impl IntoView { let dialog_check_in = move |_| { chosen_student.with(|opt_student| { if let Some(student) = opt_student.as_ref() { - check_in_action.dispatch(CheckInLoaner { student: student.clone() }); + check_in_action.dispatch(CheckInLoaner { + student: student.clone(), + }); } }) }; diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 6c06aaa..cd9f5f5 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -1,15 +1,15 @@ mod about_page; +mod api; mod comparison_test; mod home_page; +mod loaner_page; mod provider_portal; +mod scholarship_info; mod test_page; mod unauthenticated_page; -mod scholarship_info; mod utils; -mod api; -mod loaner_page; pub use self::{ - about_page::*, home_page::*, provider_portal::*, test_page::*, unauthenticated_page::*, - loaner_page::LoanerPage, comparison_test::*, + about_page::*, comparison_test::*, home_page::*, loaner_page::LoanerPage, provider_portal::*, + test_page::*, unauthenticated_page::*, }; diff --git a/src/pages/provider_portal.rs b/src/pages/provider_portal.rs index 8b13789..9274dba 100644 --- a/src/pages/provider_portal.rs +++ b/src/pages/provider_portal.rs @@ -1 +1,42 @@ +use crate::components::{Banner, DashboardButton}; +use leptos::prelude::*; +#[component] +pub fn ProviderPortal() -> impl IntoView { + view! { + <Banner + title="Provider Dashboard" + logo="PHS_Stacked_Acronym.png" + path="/" + /> + <div class="mx-auto px-6"> + <div class="container mx-auto px-6 py-8 space-y-8"> + <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> + <section class="space-y-4"> + <DashboardButton + title="Profile" + description="Edit user profile" + icon="/Person_Black.png" + path="/" + /> + <DashboardButton + title="Create Scholarship" + description="Navigate to Scholarship Creation Page" + icon="/Create_Black.png" + path="/" + /> + <DashboardButton + title="Applicants" + description="View Scholarship Applicants" + icon="/Form_Black.png" + path="/" + /> + </section> + <section class="space-y-4"> + + </section> + </div> + </div> + </div> + } +} diff --git a/src/pages/scholarship_info.rs b/src/pages/scholarship_info.rs index 63bb3ab..420dd9f 100644 --- a/src/pages/scholarship_info.rs +++ b/src/pages/scholarship_info.rs @@ -1,21 +1,24 @@ +use super::UnauthenticatedPage; +use super::api::{ + CreateScholarshipInfo, CreateTestComparisons, get_comparison_info, get_scholarship_info, +}; +use crate::common::ComparisonData; +use crate::common::{ExpandableInfo, ScholarshipFormParams}; +use crate::components::{ + ActionButton, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row, +}; use leptos::logging::log; use leptos::prelude::*; use leptos_oidc::{AuthLoaded, Authenticated}; use leptos_router::hooks::use_params; -use crate::common::{ExpandableInfo, ScholarshipFormParams}; -use crate::components::{ActionButton, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row}; -use super::UnauthenticatedPage; use traits::{AsReactive, ReactiveCapture}; -use crate::common::ComparisonData; -use super::api::{get_comparison_info, get_scholarship_info, CreateScholarshipInfo, CreateTestComparisons}; - /// # Scholarship Info Page -/// This page will handle creating or editing scholarship information given a specific +/// This page will handle creating or editing scholarship information given a specific /// scholarship ID and scholarship provider subject number. The form itself will only /// track a single scholarship (for now) and so selection of what scholarship will be edited /// must be handled *before* navigation to this page. -/// +/// /// In its current testing form, we'll be using a single fixed ID. This absolutely *MUST* be /// changed before a full release. #[component] @@ -23,17 +26,19 @@ pub fn ScholarshipInfoPage() -> impl IntoView { // let scholarship_id = String::from("test-scholarship-id2"); let url_params = use_params::<ScholarshipFormParams>(); let scholarship_id = move || { - url_params.read() + url_params + .read() .as_ref() .ok() .and_then(|params| params.id.clone()) .unwrap_or_default() }; - + let get_scholarship_action: Resource<ExpandableInfo> = Resource::new( move || scholarship_id().clone(), async |id| { - get_scholarship_info(id.clone()).await + get_scholarship_info(id.clone()) + .await // Note that this really should notify us of an error and then redirect or ask // the user to try again. .unwrap_or_else(|e| { @@ -41,23 +46,22 @@ pub fn ScholarshipInfoPage() -> impl IntoView { log!("Using default ExpandableInfo"); ExpandableInfo::new(id) }) - } + }, ); - + let get_comparison_info: Resource<Vec<ComparisonData>> = Resource::new( - move || Option::<String>::None, // There's no input to this function. + move || Option::<String>::None, // There's no input to this function. async |_| { - get_comparison_info().await - .unwrap_or_else(|e| { - log!("Failed to get comparison info: {:?}", e); - Vec::new() - }) - } + get_comparison_info().await.unwrap_or_else(|e| { + log!("Failed to get comparison info: {:?}", e); + Vec::new() + }) + }, ); - + let create_scholarship_action = ServerAction::<CreateScholarshipInfo>::new(); let create_test_comps = ServerAction::<CreateTestComparisons>::new(); - + // Remember that we only want to show the page after we've got authentication information // and if there's data for us to use. view! { @@ -69,11 +73,11 @@ pub fn ScholarshipInfoPage() -> impl IntoView { {move || Suspend::new(async move { let response = get_scholarship_action.get(); let comparisons = get_comparison_info.get(); - + response .zip(comparisons) .map(|(response, comparison_list)| view! { - <ScholarshipForm + <ScholarshipForm response=response comparison_list=comparison_list submit_action=create_scholarship_action @@ -102,7 +106,7 @@ pub fn ScholarshipInfoPage() -> impl IntoView { fn ScholarshipForm( response: ExpandableInfo, comparison_list: Vec<ComparisonData>, - submit_action: ServerAction<CreateScholarshipInfo> + submit_action: ServerAction<CreateScholarshipInfo>, ) -> impl IntoView { let reactive_info = response.as_reactive(); let elements_disabled = RwSignal::new(false); @@ -128,7 +132,7 @@ fn ScholarshipForm( // We'll collect the scholarship's name, num_awards, amount_per_award, total_awards, // fafsa_required, award_to, transcript_required, recipient_selection, essay_requirement (and prompt) // award_night_remarks - + view! { <Panel> <Row> diff --git a/src/pages/unauthenticated_page.rs b/src/pages/unauthenticated_page.rs index 44f50fb..7de72e3 100644 --- a/src/pages/unauthenticated_page.rs +++ b/src/pages/unauthenticated_page.rs @@ -1,5 +1,5 @@ -use leptos::logging::log; use crate::components::ActionButton; +use leptos::logging::log; use leptos::prelude::*; use leptos_oidc::AuthSignal; @@ -12,7 +12,7 @@ pub fn UnauthenticatedPage() -> impl IntoView { .map(|unauthenticated| unauthenticated.login_url()) }) }; - + Effect::new(move || log!("Generated login url: {:?}", login_url())); let navigate = leptos_router::hooks::use_navigate(); diff --git a/src/pages/utils.rs b/src/pages/utils.rs index b12d842..ac3f5a1 100644 --- a/src/pages/utils.rs +++ b/src/pages/utils.rs @@ -1,5 +1,5 @@ /// # Server Utilities -/// +/// /// These utility functions can only be used from the server. They are created to use server-side /// dependencies like the AWS SDK. Use of these functions will crash the front end, as these /// dependencies cannot be compiled for WASM (nor should they be). @@ -11,7 +11,7 @@ pub mod server_utils { pub async fn create_aws_config() -> SdkConfig { aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await } - + /// Creates a DynamoDB client. pub async fn create_dynamo_client() -> aws_sdk_dynamodb::Client { aws_sdk_dynamodb::Client::new(&create_aws_config().await)