From f10f01534c0e56c4ba96d5063bc0ed1770218042 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Mon, 16 Feb 2026 12:21:35 -0500 Subject: [PATCH 01/21] chore: push current changes to different PC --- src/app.rs | 33 ++++++++++++-------- src/components/data_display.rs | 38 +++++++++++++++++++++++ src/components/mod.rs | 17 ++++++----- src/pages/student/mod.rs | 30 +++++++++--------- src/pages/student/shell.rs | 9 +++--- src/pages/student/test_display.rs | 51 +++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 38 deletions(-) create mode 100644 src/components/data_display.rs create mode 100644 src/pages/student/test_display.rs diff --git a/src/app.rs b/src/app.rs index a517025..7170ee1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,12 +1,20 @@ -use crate::pages::{ProviderPortal, ScholarshipInfoPage, ProviderContactPage, LoanerShell, LoanerFallback, LoanerBorrowForm, LoanerReturnForm, AuthCallbackPage, ApplicantsPageFallback, AdminShell, AdminHomePage, AdminProviderPage, AdminScholarshipPage, AdminUtilsPage}; +use crate::components::ToastList; +use crate::components::login::ProviderLoginContext; use crate::pages::student; +use crate::pages::{ + AdminHomePage, AdminProviderPage, AdminScholarshipPage, AdminShell, AdminUtilsPage, + ApplicantsPageFallback, AuthCallbackPage, LoanerBorrowForm, LoanerFallback, LoanerReturnForm, + LoanerShell, ProviderContactPage, ProviderPortal, ScholarshipInfoPage, +}; use leptos::prelude::*; use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context}; use leptos_oidc::AuthSignal; -use leptos_router::{components::{Route, Router, Routes, ParentRoute}, path, MatchNestedRoutes}; use leptos_router::any_nested_route::IntoAnyNestedRoute; -use crate::components::login::ProviderLoginContext; -use crate::components::ToastList; +use leptos_router::{ + MatchNestedRoutes, + components::{ParentRoute, Route, Router, Routes}, + path, +}; pub fn shell(options: LeptosOptions) -> impl IntoView { view! { @@ -86,8 +94,8 @@ fn ProviderRoutes() -> impl MatchNestedRoutes + Clone { } - .into_inner() - .into_any_nested_route() + .into_inner() + .into_any_nested_route() } #[component(transparent)] @@ -99,8 +107,8 @@ fn LoanerRoutes() -> impl MatchNestedRoutes + Clone { } - .into_inner() - .into_any_nested_route() + .into_inner() + .into_any_nested_route() } #[component(transparent)] @@ -113,8 +121,8 @@ fn AdminRoutes() -> impl MatchNestedRoutes + Clone { } - .into_inner() - .into_any_nested_route() + .into_inner() + .into_any_nested_route() } #[component(transparent)] @@ -126,6 +134,7 @@ fn StudentRoutes() -> impl MatchNestedRoutes + Clone { + impl MatchNestedRoutes + Clone { } - .into_inner() - .into_any_nested_route() + .into_inner() + .into_any_nested_route() } #[component] diff --git a/src/components/data_display.rs b/src/components/data_display.rs new file mode 100644 index 0000000..8dca269 --- /dev/null +++ b/src/components/data_display.rs @@ -0,0 +1,38 @@ +use crate::common::ValueType; +use leptos::prelude::*; +use std::collections::HashMap; + +#[component] +pub fn ValueDisplay( + #[prop(into)] data_member: Signal, + #[prop(into)] data_map: Signal>, + #[prop(into)] header: Signal, +) -> impl IntoView { + let display_value = Memo::new(move |_| { + data_map + .get() + .get(&data_member.get()) + .unwrap_or(&ValueType::String(None)) + .clone() + }); + + // I've chosen to display all information - we're not going to worry about the depth of the + // information, but we are going to provide a way to collapse information like maps and lists. + // This means we need some capsule-like appearance for the information in order to facilitate + // having a header and a series of information that's buried underneath. Also, we need to consider + // how we determine what the header is because that's not really procedurally possible. + + // One possible way to handle this is a "schema." We would plan out the information beforehand + // and pass through this schema struct, allowing us to limit what's displayed, relative to the + // starting data_map. I'll write this out in a scratch file for now. + + view! { +
+
{header}
+
{move || display_value.get().to_string()}
+
+ } +} + +#[component] +pub fn MapDisplay() {} diff --git a/src/components/mod.rs b/src/components/mod.rs index 89dc30c..106d43a 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,22 +1,23 @@ mod action_button; mod banner; mod dashboard_button; +mod data_display; mod date; +mod header; +mod lists; mod loading; +pub mod login; mod multi_entry; mod panel; mod row; +mod tabs; mod text_field; +mod toasts; mod utils; mod validated_form; -mod lists; -mod toasts; -mod header; -pub mod login; -mod tabs; pub use self::{ - action_button::*, banner::*, dashboard_button::*, lists::*, - loading::*, multi_entry::*, panel::*, row::*, text_field::*, - validated_form::*, toasts::*, header::*, date::*, tabs::* + action_button::*, banner::*, dashboard_button::*, data_display::*, date::*, header::*, + lists::*, loading::*, multi_entry::*, panel::*, row::*, tabs::*, text_field::*, toasts::*, + validated_form::*, }; diff --git a/src/pages/student/mod.rs b/src/pages/student/mod.rs index 5c3e5fa..cfe4aaa 100644 --- a/src/pages/student/mod.rs +++ b/src/pages/student/mod.rs @@ -1,24 +1,26 @@ -mod shell; -mod home_page; -mod demographics_page; +mod academics_page; mod additional_page; mod athletics_page; -mod academics_page; +mod demographics_page; +mod eligibility_page; mod extracurriculars_page; -mod work_experience_page; -mod university_page; mod family_info_page; mod form_setup; -mod eligibility_page; +mod home_page; +mod shell; +mod test_display; +mod university_page; +mod work_experience_page; -pub use shell::*; -pub use home_page::*; -pub use demographics_page::*; +pub use academics_page::*; pub use additional_page::*; pub use athletics_page::*; -pub use academics_page::*; +pub use demographics_page::*; +pub use eligibility_page::*; pub use extracurriculars_page::*; -pub use work_experience_page::*; -pub use university_page::*; pub use family_info_page::*; -pub use eligibility_page::*; \ No newline at end of file +pub use home_page::*; +pub use shell::*; +pub use test_display::*; +pub use university_page::*; +pub use work_experience_page::*; diff --git a/src/pages/student/shell.rs b/src/pages/student/shell.rs index fe958d3..023f24e 100644 --- a/src/pages/student/shell.rs +++ b/src/pages/student/shell.rs @@ -1,9 +1,9 @@ +use crate::common::{SubTabInfo, TabInfo}; +use crate::components::login::StudentLoginContext; +use crate::components::{Banner, Loading, TabSidebarList}; +use crate::pages::UnauthenticatedPage; use leptos::prelude::*; use leptos_oidc::{AuthLoaded, Authenticated}; -use crate::common::{SubTabInfo, TabInfo}; -use crate::components::{Banner, Loading, TabSidebarList}; -use crate::components::login::StudentLoginContext; -use crate::pages::UnauthenticatedPage; #[component] pub fn StudentShell() -> impl IntoView { @@ -39,6 +39,7 @@ pub fn StudentShell() -> impl IntoView { ), ), TabInfo::new("Eligible Scholarships", "eligibility", None), + TabInfo::new("Testing Display", "testing", None) ] /> diff --git a/src/pages/student/test_display.rs b/src/pages/student/test_display.rs new file mode 100644 index 0000000..b5c7cfa --- /dev/null +++ b/src/pages/student/test_display.rs @@ -0,0 +1,51 @@ +use crate::components::{Loading, ValueDisplay}; +use crate::pages::api::students::get_all_student_data; +use crate::utils::get_user_claims; +use leptos::prelude::*; + +#[component] +pub fn StudentTestDisplay() -> impl IntoView { + let user_claims = get_user_claims(); + let user_id = Memo::new(move |_| user_claims.get().map(|info| info.claims.subject.clone())); + + let resource = Resource::new( + move || user_id.get().unwrap_or_default(), + async move |id| get_all_student_data(id).await, + ); + + view! { + + {move || { + resource + .get() + .map(|data_map| { + let Ok(map) = data_map else { + return view! {}.into_any(); + }; + let data_map = StoredValue::new(map); + + view! { +
+ + } + } + /> +
+ } + .into_any() + }) + }} +
+ } +} From 04ba0ff1eaac903498b865785ecafd730766f651 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Tue, 17 Feb 2026 13:58:01 -0500 Subject: [PATCH 02/21] feat: begin implementation of value display --- src/common/mod.rs | 3 +- src/common/schema_node.rs | 97 ++++++++++++++ src/common/value_type.rs | 4 +- src/components/data_display.rs | 207 +++++++++++++++++++++++++++--- src/pages/student/test_display.rs | 68 +++++----- 5 files changed, 325 insertions(+), 54 deletions(-) create mode 100644 src/common/schema_node.rs diff --git a/src/common/mod.rs b/src/common/mod.rs index cd627ea..a561e44 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -8,8 +8,9 @@ mod user; mod value_type; pub mod animations; mod tab_info; +mod schema_node; pub use self::{ comparison::*, date_info::*, expandable_info::*, form_params::*, form_schema::*, - submit_status::*, user::*, value_type::*, tab_info::*, + submit_status::*, user::*, value_type::*, tab_info::*, schema_node::*, }; diff --git a/src/common/schema_node.rs b/src/common/schema_node.rs new file mode 100644 index 0000000..06062cd --- /dev/null +++ b/src/common/schema_node.rs @@ -0,0 +1,97 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum SchemaType { + String, + Number, + List, + Map +} + +/// # Schema Node +/// +/// This is used to create a schema that is used in the [`ValueDisplay`] component. It stores all +/// required information for the display, specifically where to find the data and how to display it. +/// It covers all `ValueType` variants, however for data storage and DX it uses a `SchemaType` +/// instead. +/// +/// Some examples are as follows, using the fluent builder notation: +/// - Displaying a single `ValueType::String` from the data map at the key `example_one`: +/// ``` +/// SchemaNode::new(SchemaType::String).header("The example string") +/// ``` +/// - Displaying a `ValueType::List` which contains only `ValueType::String` entries: +/// ``` +/// SchemaNode::new(SchemaType::List) +/// .header("The example list") +/// .item_template(SchemaNode::new(SchemaType::String).header("")) +/// ``` +/// - Displaying a `ValueType::Map` which contains a name and age: +/// ``` +/// SchemaType::new(SchemaType::Map) +/// .header("The example map") +/// .child("first_name", SchemaNode::new(SchemaType::String).header("First Name:")) +/// .child("age", SchemaNode::new(SchemaType::Number).header("Age:")) +/// ``` +/// - Put it all together: a `ValueType::List` which contains `ValueType::Map` entries, each of +/// which also contains the name and age from the previous example: +/// ``` +/// SchemaNode::new(SchemaType::List) +/// .header("The example map list") +/// .item_template( +/// SchemaNode::new(SchemaType::Map) +/// .header("The example map from earlier") +/// .child("first_name", SchemaNode::new(SchemaType::String).header("First Name:")) +/// .child("age", SchemaNode::new(SchemaType::Number).header("Age:")) +/// ) +/// ``` +/// +/// This schema allows for definitive typing and styling for every level of the data display. +/// +/// ## Important Notes +/// +/// This schema does not currently support display of a single nested member, like a single map +/// within a list. This means that you will have to display any listed or mapped information as a +/// list. Consider this when designing the data. +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct SchemaNode { + /// The datatype of the data found using the `data_member` key. + pub data_type: SchemaType, + /// The header of this display. + pub header: String, + /// When used with a SchemaType::List, this determines the datatype and children for every entry + /// in the list. Some examples include a list of maps, where we use the `item_template` to define + /// the SchemaType::Map, and all its children. This is then mapped across all entries in the list, + /// where any entries that do not match the template are skipped. + pub item_template: Option>, + /// When used with a SchemaType::Map, this determines what children from the map are displayed + /// and how. It is ignored with all other SchemaType variants. + pub children: HashMap, +} + +impl SchemaNode { + pub fn new(data_type: SchemaType) -> Self { + SchemaNode { + data_type, + header: "".to_string(), + item_template: None, + children: HashMap::new(), + } + } + + pub fn header(mut self, header: impl Into) -> Self { + self.header = header.into(); + self + } + + pub fn child(mut self, data_member: impl Into, node: SchemaNode) -> Self { + self.children.insert(data_member.into(), node); + self + } + + pub fn item_template(mut self, node: SchemaNode) -> Self { + self.item_template = Some(Box::new(node)); + self + } +} diff --git a/src/common/value_type.rs b/src/common/value_type.rs index 6f54016..90e1f2f 100644 --- a/src/common/value_type.rs +++ b/src/common/value_type.rs @@ -105,8 +105,8 @@ impl Default for ValueType { impl Display for ValueType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let str = match self { - ValueType::String(s) => s.clone().unwrap_or("".to_owned()), - ValueType::Number(n) => n.clone().unwrap_or("".to_owned()), + ValueType::String(s) => s.clone().unwrap_or_default(), + ValueType::Number(n) => n.clone().unwrap_or_default(), ValueType::List(l) => format!("{l:?}"), ValueType::Map(m) => format!("{m:?}"), }; diff --git a/src/components/data_display.rs b/src/components/data_display.rs index 8dca269..036fde6 100644 --- a/src/components/data_display.rs +++ b/src/components/data_display.rs @@ -1,38 +1,203 @@ -use crate::common::ValueType; +use crate::common::{SchemaNode, SchemaType, ValueType}; use leptos::prelude::*; use std::collections::HashMap; +use leptos::logging::debug_log; #[component] pub fn ValueDisplay( - #[prop(into)] data_member: Signal, + #[prop(into)] schema: Signal, + #[prop(into, optional)] data_member: Signal, #[prop(into)] data_map: Signal>, - #[prop(into)] header: Signal, ) -> impl IntoView { - let display_value = Memo::new(move |_| { - data_map - .get() - .get(&data_member.get()) - .unwrap_or(&ValueType::String(None)) - .clone() + let data_value = Memo::new(move |_| { + if data_member.get().is_empty() { + ValueType::Map(Some(data_map.get())) + } else { + data_map.get() + .get(&data_member.get()) + .map(|v| v.clone()) + .unwrap_or_default() + } }); - // I've chosen to display all information - we're not going to worry about the depth of the - // information, but we are going to provide a way to collapse information like maps and lists. - // This means we need some capsule-like appearance for the information in order to facilitate - // having a header and a series of information that's buried underneath. Also, we need to consider - // how we determine what the header is because that's not really procedurally possible. + view! { + {move || { + match schema.get().data_type { + SchemaType::String | SchemaType::Number => view! { + + }.into_any(), + SchemaType::List => view! { + + }.into_any(), + SchemaType::Map => view! { + + }.into_any() + } + }} + } +} - // One possible way to handle this is a "schema." We would plan out the information beforehand - // and pass through this schema struct, allowing us to limit what's displayed, relative to the - // starting data_map. I'll write this out in a scratch file for now. +#[component] +fn PrimitiveDisplay( + #[prop(into)] header: Signal, + #[prop(into)] value: Signal +) -> impl IntoView { + let display_string = Memo::new(move |_| { + let value = value.get(); + match value { + ValueType::String(_) | ValueType::Number(_) => { + value.to_string() + } + _ => { + "Value is not a string.".to_string() + } + } + }); view! { -
-
{header}
-
{move || display_value.get().to_string()}
+
+ +
{header}
+
+
{display_string}
} } #[component] -pub fn MapDisplay() {} +fn ListDisplay( + #[prop(into)] item_template: Signal>, + #[prop(into)] value: Signal, + #[prop(into)] header: Signal, +) -> impl IntoView { + // Unwrap the value and check if it's the correct type. To be displayed with this component, it + // MUST be a ValueType::List. + let display_list = Memo::new(move |_| { + let ValueType::List(Some(list)) = value.get() else { + return Vec::new(); + }; + + list.into_iter().enumerate().collect::>() + }); + + let is_valid = Memo::new(move |_| { + matches!(value.get(), ValueType::List(_)) + }); + + let item_header = Memo::new(move |_| { + item_template.get().header + }); + + view! { +
+
{header}
+ view! { + + }.into_any(), + SchemaType::List => view! { +
"Nested lists are not currently supported for display."
+ }.into_any(), + SchemaType::Map => view! { + + }.into_any() + } + } + /> +
+ } +} + +#[component] +fn MapDisplay( + #[prop(into)] value: Signal, + #[prop(into)] header: Signal, + #[prop(into)] schema: Signal>, +) -> impl IntoView { + // Unwrap the value and check if it is the correct type. It must be a ValueType::Map. + let display_map = Memo::new(move |_| { + let ValueType::Map(Some(map)) = value.get() else { + return HashMap::new(); + }; + + map + }); + + Effect::new(move || { + debug_log!("Current map values: {:?}", display_map.get()); + }); + + let is_valid = Memo::new(move |_| { + matches!(value.get(), ValueType::Map(_)) + }); + + view! { +
+
{header}
+
+ view! { + + }.into_any(), + SchemaType::List => { + let template = child_schema.item_template + .unwrap_or(Box::from(SchemaNode::new(SchemaType::List))); + + view! { + + } + }.into_any(), + SchemaType::Map => view! { + + }.into_any() + } + } + /> +
+
+ } +} diff --git a/src/pages/student/test_display.rs b/src/pages/student/test_display.rs index b5c7cfa..a0fa461 100644 --- a/src/pages/student/test_display.rs +++ b/src/pages/student/test_display.rs @@ -2,9 +2,24 @@ use crate::components::{Loading, ValueDisplay}; use crate::pages::api::students::get_all_student_data; use crate::utils::get_user_claims; use leptos::prelude::*; +use crate::common::{SchemaNode, SchemaType}; #[component] pub fn StudentTestDisplay() -> impl IntoView { + let create_student_display_schema = move || { + SchemaNode::new(SchemaType::Map) + .header("Student Information:") + .child("first_name", SchemaNode::new(SchemaType::String).header("First Name:")) + .child("last_name", SchemaNode::new(SchemaType::String).header("Last Name:")) + .child("sports_participation", SchemaNode::new(SchemaType::List) + .header("List of Sports Activities") + .item_template(SchemaNode::new(SchemaType::Map) + .child("sport_name", SchemaNode::new(SchemaType::String).header("Sport Name:")) + .child("achievements", SchemaNode::new(SchemaType::String).header("Special Achievements")) + ) + ) + }; + let user_claims = get_user_claims(); let user_id = Memo::new(move |_| user_claims.get().map(|info| info.claims.subject.clone())); @@ -13,39 +28,32 @@ pub fn StudentTestDisplay() -> impl IntoView { async move |id| get_all_student_data(id).await, ); + let schema = StoredValue::new(create_student_display_schema()); + view! { - {move || { - resource - .get() - .map(|data_map| { - let Ok(map) = data_map else { - return view! {}.into_any(); - }; - let data_map = StoredValue::new(map); +
+ {move || { + resource + .get() + .map(|data_map| { + let Ok(map) = data_map else { + return view! {}.into_any(); + }; + let data_map = StoredValue::new(map); - view! { -
- - } - } - /> -
- } - .into_any() - }) - }} + view! { +
+ +
+ } + .into_any() + }) + }} +
} } From 0816f2da8be19d3c19c39aed1efb17015f53ea23 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Wed, 18 Feb 2026 09:00:15 -0500 Subject: [PATCH 03/21] feat: add indexmap dependency --- Cargo.lock | 3 +++ Cargo.toml | 1 + src/common/schema_node.rs | 5 +++-- src/components/data_display.rs | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 963391a..5290b50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1869,6 +1869,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -3026,6 +3028,7 @@ dependencies = [ "chrono", "console_error_panic_hook", "icondata", + "indexmap", "leptos", "leptos_animate", "leptos_axum", diff --git a/Cargo.toml b/Cargo.toml index 749ffdd..c29aaf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ macros = { path = "./macros" } traits = { path = "./traits" } icondata = { version = "0.7.0", features = ["font-awesome"] } leptos_icons = "0.7.1" +indexmap = { version = "2.13.0", features = ["serde"] } [features] default = ["ssr"] diff --git a/src/common/schema_node.rs b/src/common/schema_node.rs index 06062cd..2e1f943 100644 --- a/src/common/schema_node.rs +++ b/src/common/schema_node.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -67,7 +68,7 @@ pub struct SchemaNode { pub item_template: Option>, /// When used with a SchemaType::Map, this determines what children from the map are displayed /// and how. It is ignored with all other SchemaType variants. - pub children: HashMap, + pub children: IndexMap, } impl SchemaNode { @@ -76,7 +77,7 @@ impl SchemaNode { data_type, header: "".to_string(), item_template: None, - children: HashMap::new(), + children: IndexMap::new(), } } diff --git a/src/components/data_display.rs b/src/components/data_display.rs index 036fde6..5c2a1d1 100644 --- a/src/components/data_display.rs +++ b/src/components/data_display.rs @@ -1,6 +1,7 @@ use crate::common::{SchemaNode, SchemaType, ValueType}; use leptos::prelude::*; use std::collections::HashMap; +use indexmap::IndexMap; use leptos::logging::debug_log; #[component] @@ -134,7 +135,7 @@ fn ListDisplay( fn MapDisplay( #[prop(into)] value: Signal, #[prop(into)] header: Signal, - #[prop(into)] schema: Signal>, + #[prop(into)] schema: Signal>, ) -> impl IntoView { // Unwrap the value and check if it is the correct type. It must be a ValueType::Map. let display_map = Memo::new(move |_| { From c850ac208ff9212a9063b1bc9cccbe843b8e015e Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Fri, 20 Feb 2026 08:56:13 -0500 Subject: [PATCH 04/21] feat: finish initial data display component --- src/components/data_display.rs | 242 +++++++++++++++----------- src/pages/student/eligibility_page.rs | 91 +++------- src/pages/student/test_display.rs | 40 +++-- 3 files changed, 202 insertions(+), 171 deletions(-) diff --git a/src/components/data_display.rs b/src/components/data_display.rs index 5c2a1d1..1534bf9 100644 --- a/src/components/data_display.rs +++ b/src/components/data_display.rs @@ -1,8 +1,8 @@ use crate::common::{SchemaNode, SchemaType, ValueType}; -use leptos::prelude::*; -use std::collections::HashMap; use indexmap::IndexMap; use leptos::logging::debug_log; +use leptos::prelude::*; +use std::collections::HashMap; #[component] pub fn ValueDisplay( @@ -14,7 +14,8 @@ pub fn ValueDisplay( if data_member.get().is_empty() { ValueType::Map(Some(data_map.get())) } else { - data_map.get() + data_map + .get() .get(&data_member.get()) .map(|v| v.clone()) .unwrap_or_default() @@ -24,55 +25,101 @@ pub fn ValueDisplay( view! { {move || { match schema.get().data_type { - SchemaType::String | SchemaType::Number => view! { - - }.into_any(), - SchemaType::List => view! { - - }.into_any(), - SchemaType::Map => view! { - - }.into_any() + SchemaType::String | SchemaType::Number => { + view! { + + } + .into_any() + } + SchemaType::List => { + view! { + + } + .into_any() + } + SchemaType::Map => { + view! { + + } + .into_any() + } } }} } } +#[component] +fn DisplayWrapper( + #[prop(into, optional)] header: Signal, + #[prop(into, optional)] nested: Signal, + children: Children, +) -> impl IntoView { + // This component is the wrapper that is used in the rest of the display components. + // It allows for simpler styling of the header and capsule, along with simple collapsable + // functionality. + + let expanded = RwSignal::new(true); + + view! { +
+
+ {header} +
+
+
+
{children()}
+
+
+
+ } +} + #[component] fn PrimitiveDisplay( #[prop(into)] header: Signal, - #[prop(into)] value: Signal + #[prop(into)] value: Signal, + #[prop(into, optional)] nested: Signal, ) -> impl IntoView { let display_string = Memo::new(move |_| { let value = value.get(); match value { - ValueType::String(_) | ValueType::Number(_) => { - value.to_string() - } - _ => { - "Value is not a string.".to_string() - } + ValueType::String(_) | ValueType::Number(_) => value.to_string(), + _ => "Value is not a string.".to_string(), } }); view! { -
- -
{header}
-
+
{display_string}
-
+ } } @@ -81,6 +128,7 @@ fn ListDisplay( #[prop(into)] item_template: Signal>, #[prop(into)] value: Signal, #[prop(into)] header: Signal, + #[prop(into, optional)] nested: Signal, ) -> impl IntoView { // Unwrap the value and check if it's the correct type. To be displayed with this component, it // MUST be a ValueType::List. @@ -89,45 +137,46 @@ fn ListDisplay( return Vec::new(); }; - list.into_iter().enumerate().collect::>() + list.into_iter() + .enumerate() + .collect::>() }); - let is_valid = Memo::new(move |_| { - matches!(value.get(), ValueType::List(_)) - }); + let is_valid = Memo::new(move |_| matches!(value.get(), ValueType::List(_))); - let item_header = Memo::new(move |_| { - item_template.get().header - }); + let item_header = Memo::new(move |_| item_template.get().header); view! { -
-
{header}
+ view! { - - }.into_any(), - SchemaType::List => view! { -
"Nested lists are not currently supported for display."
- }.into_any(), - SchemaType::Map => view! { - - }.into_any() + SchemaType::String | SchemaType::Number => { + view! { }.into_any() + } + SchemaType::List => { + view! { +
"Nested lists are not currently supported for display."
+ } + .into_any() + } + SchemaType::Map => { + let map_header = format!("{} {}", item_template.get().header, idx + 1); + view! { + + } + .into_any() + } } } /> -
+ } } @@ -136,6 +185,7 @@ fn MapDisplay( #[prop(into)] value: Signal, #[prop(into)] header: Signal, #[prop(into)] schema: Signal>, + #[prop(into, optional)] nested: Signal, ) -> impl IntoView { // Unwrap the value and check if it is the correct type. It must be a ValueType::Map. let display_map = Memo::new(move |_| { @@ -150,55 +200,51 @@ fn MapDisplay( debug_log!("Current map values: {:?}", display_map.get()); }); - let is_valid = Memo::new(move |_| { - matches!(value.get(), ValueType::Map(_)) - }); + let is_valid = Memo::new(move |_| matches!(value.get(), ValueType::Map(_))); view! { -
-
{header}
-
- + { + view! { + + } + .into_any() + } + SchemaType::List => { + let template = child_schema + .item_template + .unwrap_or(Box::from(SchemaNode::new(SchemaType::List))); - match child_schema.data_type { - SchemaType::String | SchemaType::Number => view! { - - }.into_any(), - SchemaType::List => { - let template = child_schema.item_template - .unwrap_or(Box::from(SchemaNode::new(SchemaType::List))); - - view! { - - } - }.into_any(), - SchemaType::Map => view! { + } + .into_any() + } + SchemaType::Map => { + view! { - }.into_any() + } + .into_any() } } - /> -
-
+ } + /> + } } diff --git a/src/pages/student/eligibility_page.rs b/src/pages/student/eligibility_page.rs index 9f761dc..b880f29 100644 --- a/src/pages/student/eligibility_page.rs +++ b/src/pages/student/eligibility_page.rs @@ -1,9 +1,10 @@ +use crate::common::{ComparisonData, ExpandableInfo, SchemaNode, SchemaType, ValueType}; +use crate::components::ValueDisplay; +use crate::pages::api::students::get_all_student_data; +use crate::pages::api::{get_all_scholarship_info, get_comparison_info}; +use crate::utils::get_user_claims; use leptos::logging::debug_log; use leptos::prelude::*; -use crate::common::{ComparisonData, ExpandableInfo, ValueType}; -use crate::pages::api::{get_all_scholarship_info, get_comparison_info}; -use crate::pages::api::students::get_all_student_data; -use crate::utils::get_user_claims; #[component] pub fn StudentEligibilityPage() -> impl IntoView { @@ -13,37 +14,28 @@ pub fn StudentEligibilityPage() -> impl IntoView { // Then, for each scholarship found, we'll check every relation and filter out scholarships that // do not pass the check. We'll show some sort of simple list to indicate what scholarships passed // the check, and another to indicate which ones didn't. - + // My only reservation with showing the scholarships that aren't valid is that people will want // to know why. That's hard information to keep track of right now. - + let user_claims = get_user_claims(); - let user_id = Memo::new(move |_| { - user_claims.get() - .map(|info| info.claims.subject.clone()) - }); - + let user_id = Memo::new(move |_| user_claims.get().map(|info| info.claims.subject.clone())); + let refresh_trigger = Trigger::new(); - + let scholarships_resource = Resource::new( move || refresh_trigger.track(), - async move |_| { - get_all_scholarship_info().await - } + async move |_| get_all_scholarship_info().await, ); let relations_resource = Resource::new( move || refresh_trigger.track(), - async move |_| { - get_comparison_info().await - } + async move |_| get_comparison_info().await, ); let student_resource = Resource::new( move || (user_id.get().unwrap_or_default(), refresh_trigger.track()), - async move |(user_id, _)| { - get_all_student_data(user_id).await - } + async move |(user_id, _)| get_all_student_data(user_id).await, ); - + view! {
@@ -131,15 +123,6 @@ pub fn StudentEligibilityPage() -> impl IntoView { } }) .collect::>(); - - // Each scholarship will return Some(itself), or None. This is will be automatically - // filtered based on the information. Invalid scholarships are None, along with those - // that fail validation for any reason. Scholarships that are valid or did not choose - // any requirements are Some(scholarship). - - // We want to cast to a list, and then map the list to a new - // one that contains the correct ComparisonData. - view! {
{format!("Valid scholarships: {}", valid_list.len())}
impl IntoView { key=|scholarship| { scholarship.subject.clone() } children=move |scholarship| { let scholarship = StoredValue::new(scholarship); - let scholarship_name = Memo::new(move |_| { - scholarship - .read_value() - .data - .get("name") - .unwrap_or(&ValueType::String(None)) - .as_string() - .ok() - .flatten() - .unwrap_or_default() - }); - let scholarship_essay_prompt = Memo::new(move |_| { - scholarship - .read_value() - .data - .get("essay_prompt") - .unwrap_or(&ValueType::String(None)) - .as_string() - .ok() - .flatten() - .unwrap_or_default() - }); - let essay_required = Memo::new(move |_| { - !scholarship_essay_prompt.get().is_empty() - }); + let display_schema = SchemaNode::new(SchemaType::Map) + .header("Eligible Scholarship") + .child( + "name", + SchemaNode::new(SchemaType::String) + .header("Scholarship Name"), + ) + .child( + "essay_prompt", + SchemaNode::new(SchemaType::String).header("Essay Prompt"), + ); view! { -
-
{scholarship_name}
- -
{scholarship_essay_prompt}
-
-
+ } } /> diff --git a/src/pages/student/test_display.rs b/src/pages/student/test_display.rs index a0fa461..a66d8eb 100644 --- a/src/pages/student/test_display.rs +++ b/src/pages/student/test_display.rs @@ -1,22 +1,38 @@ +use crate::common::{SchemaNode, SchemaType}; use crate::components::{Loading, ValueDisplay}; use crate::pages::api::students::get_all_student_data; use crate::utils::get_user_claims; use leptos::prelude::*; -use crate::common::{SchemaNode, SchemaType}; #[component] pub fn StudentTestDisplay() -> impl IntoView { let create_student_display_schema = move || { SchemaNode::new(SchemaType::Map) - .header("Student Information:") - .child("first_name", SchemaNode::new(SchemaType::String).header("First Name:")) - .child("last_name", SchemaNode::new(SchemaType::String).header("Last Name:")) - .child("sports_participation", SchemaNode::new(SchemaType::List) - .header("List of Sports Activities") - .item_template(SchemaNode::new(SchemaType::Map) - .child("sport_name", SchemaNode::new(SchemaType::String).header("Sport Name:")) - .child("achievements", SchemaNode::new(SchemaType::String).header("Special Achievements")) - ) + .header("Student Information") + .child( + "first_name", + SchemaNode::new(SchemaType::String).header("First Name"), + ) + .child( + "last_name", + SchemaNode::new(SchemaType::String).header("Last Name"), + ) + .child( + "sports_participation", + SchemaNode::new(SchemaType::List) + .header("Sports Activities") + .item_template( + SchemaNode::new(SchemaType::Map) + .header("Sport") + .child( + "sport_name", + SchemaNode::new(SchemaType::String).header("Sport Name"), + ) + .child( + "achievements", + SchemaNode::new(SchemaType::String).header("Special Achievements"), + ), + ), ) }; @@ -31,8 +47,9 @@ pub fn StudentTestDisplay() -> impl IntoView { let schema = StoredValue::new(create_student_display_schema()); view! { +
-
+
{move || { resource .get() @@ -55,5 +72,6 @@ pub fn StudentTestDisplay() -> impl IntoView { }}
+
} } From 1afacf600894beec3cd57f4186e3f0adbeac366a Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Fri, 20 Feb 2026 09:16:55 -0500 Subject: [PATCH 05/21] docs: add docs to data display --- src/components/data_display.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/data_display.rs b/src/components/data_display.rs index 1534bf9..3ab3173 100644 --- a/src/components/data_display.rs +++ b/src/components/data_display.rs @@ -4,6 +4,9 @@ use leptos::logging::debug_log; use leptos::prelude::*; use std::collections::HashMap; +/// # Value Display Component +/// This component displays any data from a given `HashMap`. It requires a schema +/// to tell it how to display the information, which is covered in the [`SchemaNode`] documentation. #[component] pub fn ValueDisplay( #[prop(into)] schema: Signal, @@ -67,16 +70,16 @@ pub fn ValueDisplay( } } +/// # Display Wrapper Component +/// This component is designed to wrap information for the [`ValueDisplay`] component. It allows +/// for simple uniform styling of the header and capsule, along with the ability for users to +/// collapse the information. #[component] fn DisplayWrapper( #[prop(into, optional)] header: Signal, #[prop(into, optional)] nested: Signal, children: Children, ) -> impl IntoView { - // This component is the wrapper that is used in the rest of the display components. - // It allows for simpler styling of the header and capsule, along with simple collapsable - // functionality. - let expanded = RwSignal::new(true); view! { @@ -102,6 +105,8 @@ fn DisplayWrapper( } } +/// # Primitive Value Display Component +/// This component allows for the display of a `ValueType::String` or `ValueType::Number`. #[component] fn PrimitiveDisplay( #[prop(into)] header: Signal, @@ -123,6 +128,10 @@ fn PrimitiveDisplay( } } +/// # List Value Display Component +/// This component allows for the display of a `ValueType::List`. It will show an error if passed +/// anything other than a list. It requires an item template that tells the list how to display +/// each value inside of it, most specifically the type of data inside of it. #[component] fn ListDisplay( #[prop(into)] item_template: Signal>, @@ -180,6 +189,10 @@ fn ListDisplay( } } +/// # Map Data Display Component +/// This component allows for the display of a `ValueType::Map`. The nested information must be defined +/// by a [`SchemaNode`], which tells the display what keys in the value map to search for the +/// display information. #[component] fn MapDisplay( #[prop(into)] value: Signal, From be54aa8fd24d6048eb8a615471212fb7ca16f4e1 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Fri, 20 Feb 2026 09:58:16 -0500 Subject: [PATCH 06/21] feat: change Into implementation to From --- src/common/value_type.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/common/value_type.rs b/src/common/value_type.rs index 90e1f2f..f70cf2b 100644 --- a/src/common/value_type.rs +++ b/src/common/value_type.rs @@ -124,20 +124,32 @@ impl From<&AttributeValue> for ValueType { AttributeValue::S(s) => ValueType::String(Some(s.clone())), AttributeValue::N(n) => ValueType::Number(Some(n.clone())), AttributeValue::L(l) => ValueType::List(Some(l.iter().map(|v| v.into()).collect())), - AttributeValue::M(m) => ValueType::Map(Some(m.iter().map(|(k, v)| (k.clone(), v.into())).collect())), + AttributeValue::M(m) => { + ValueType::Map(Some(m.iter().map(|(k, v)| (k.clone(), v.into())).collect())) + } _ => ValueType::default(), } } } #[cfg(feature = "ssr")] -impl Into for ValueType { - fn into(self) -> AttributeValue { - match self { +impl From for AttributeValue { + fn from(value: ValueType) -> Self { + match value { ValueType::String(s) => AttributeValue::S(s.unwrap_or_default()), ValueType::Number(n) => AttributeValue::N(n.unwrap_or_default()), - ValueType::List(l) => AttributeValue::L(l.unwrap_or_default().into_iter().map(|v| v.into()).collect()), - ValueType::Map(m) => AttributeValue::M(m.unwrap_or_default().into_iter().map(|(k, v)| (k, v.into())).collect()), + ValueType::List(l) => AttributeValue::L( + l.unwrap_or_default() + .into_iter() + .map(|v| v.into()) + .collect(), + ), + ValueType::Map(m) => AttributeValue::M( + m.unwrap_or_default() + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + ), } } } From c98621aefa1e85339f8eae9bc0f94a236a2a364a Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Fri, 27 Feb 2026 07:39:31 -0500 Subject: [PATCH 07/21] feat: implement CSV export of scholarships --- Cargo.lock | 22 ++++ Cargo.toml | 1 + src/app.rs | 1 + src/pages/admin/utils_page.rs | 157 ++++++---------------- src/pages/api/exports.rs | 237 ++++++++++++++++++++++++++++++++++ src/pages/api/mod.rs | 13 +- 6 files changed, 309 insertions(+), 122 deletions(-) create mode 100644 src/pages/api/exports.rs diff --git a/Cargo.lock b/Cargo.lock index 5290b50..f753494 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -829,6 +829,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.20.11" @@ -3027,6 +3048,7 @@ dependencies = [ "base64 0.22.1", "chrono", "console_error_panic_hook", + "csv", "icondata", "indexmap", "leptos", diff --git a/Cargo.toml b/Cargo.toml index c29aaf5..401029d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ traits = { path = "./traits" } icondata = { version = "0.7.0", features = ["font-awesome"] } leptos_icons = "0.7.1" indexmap = { version = "2.13.0", features = ["serde"] } +csv = "1.4.0" [features] default = ["ssr"] diff --git a/src/app.rs b/src/app.rs index 7170ee1..75517b3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -74,6 +74,7 @@ pub fn AppWithRoutes() -> impl IntoView { + diff --git a/src/pages/admin/utils_page.rs b/src/pages/admin/utils_page.rs index 7b8e7d1..202f770 100644 --- a/src/pages/admin/utils_page.rs +++ b/src/pages/admin/utils_page.rs @@ -1,17 +1,21 @@ -use chrono::DateTime; +use crate::common::{DateInfo, DateRange}; use crate::components::ActionButton; +use crate::pages::api::exports::get_scholarship_csv; +use crate::pages::api::{CreateDates, CreateTestComparisons, exports::GetScholarshipCsv}; +use chrono::DateTime; use leptos::prelude::*; -use crate::common::{DateInfo, DateRange}; -use crate::pages::api::{CreateDates, CreateTestComparisons}; +use leptos::task::spawn_local; +use leptos::wasm_bindgen::JsCast; +use leptos::web_sys::{Blob, HtmlAnchorElement, Url, js_sys}; fn get_important_dates() -> Vec { - vec! [ + vec![ DateInfo { id: uuid::Uuid::new_v4().to_string(), title: "Provider Forms Open".to_string(), date: DateRange::Range( DateTime::parse_from_rfc3339("2026-01-20T07:15:00-05:00").unwrap(), - DateTime::parse_from_rfc3339("2026-03-03T23:59:00-05:00").unwrap() + DateTime::parse_from_rfc3339("2026-03-03T23:59:00-05:00").unwrap(), ), description: "".to_string(), }, @@ -20,7 +24,7 @@ fn get_important_dates() -> Vec { title: "Student Forms Open".to_string(), date: DateRange::Range( DateTime::parse_from_rfc3339("2026-03-03T07:15:00-05:00").unwrap(), - DateTime::parse_from_rfc3339("2026-03-27T14:05:00-05:00").unwrap() + DateTime::parse_from_rfc3339("2026-03-27T14:05:00-05:00").unwrap(), ), description: "".to_string(), }, @@ -29,7 +33,7 @@ fn get_important_dates() -> Vec { title: "Scholarship Decision Window".to_string(), date: DateRange::Range( DateTime::parse_from_rfc3339("2026-04-10T07:15:00-05:00").unwrap(), - DateTime::parse_from_rfc3339("2026-05-01T23:59:00-05:00").unwrap() + DateTime::parse_from_rfc3339("2026-05-01T23:59:00-05:00").unwrap(), ), description: "Start date may change if available sooner.".to_string(), }, @@ -52,127 +56,48 @@ fn get_important_dates() -> Vec { ] } -#[server] -async fn create_scholarship_export() -> Result, ServerFnError> { - // Exports olarship information - specifically the name of the scholarship, the name of the - // provider, and the scholarship's requirements. - // This means we'll need to get all the relations, all the scholarships, and all the contact info. - use crate::utils::server::create_dynamo_client; - use aws_sdk_dynamodb::client::Client; - use crate::pages::api::{get_comparison_info, SCHOLARSHIPS_TABLE, PROVIDER_CONTACT_TABLE}; - use std::collections::HashMap; - use crate::common::ValueType; - - let scan_table_items = async |client: &Client, table_name: &str| { - client.scan() - .table_name(table_name) - .send() - .await - .ok().and_then(|res| res.items) - .unwrap_or_default() - }; - - #[derive(serde::Serialize, serde::Deserialize, Debug)] - struct ScholarshipInfo { - name: String, - provider_name: String, - relations: Vec, - } - - let client = create_dynamo_client().await; - - let scholarships = dbg!(scan_table_items(&client, SCHOLARSHIPS_TABLE) - .await - .iter().map(|item| { - item.into_iter() - .map(|(k, v)| (k.clone(), ValueType::from(v))) - .collect() - }).collect::>>() - ); - let contacts = dbg!( - scan_table_items(&client, PROVIDER_CONTACT_TABLE).await - .iter().map(|item| { - item.into_iter() - .map(|(k, v)| (k.clone(), ValueType::from(v))) - .collect() - }).collect::>>() - ); - let db_relations = dbg!( - get_comparison_info().await.unwrap_or_default() - ); - - // Build a new structure containing some of the scholarship's information. - let resolved = scholarships.into_iter() - .map(|scholarship| { - // We just want the scholarship's name, the relations, and the provider's name. - // In here, we're going to resolve all the scholarship's information to provide this in - // a readable format. - let name = scholarship.get("name") - .unwrap_or(&ValueType::String(None)) - .to_string(); - - // The list of relations - we're going to search the master list for each comparison's - // name. It's structured as a map of lists, or a ValueType::Map(Option>)>>) - let relations = scholarship.get("requirements") - .unwrap_or(&ValueType::Map(None)) - .as_map().ok().flatten() - .unwrap_or_default() // We have our map of categories, now we need all the values inside each category. - .iter() - .map(|(_, list)| { - list.as_list().ok().flatten() - .unwrap_or_default() - }) - .flatten() - .collect::>() // This is a Vec - .iter().map(|val| val.clone().as_string().ok().flatten().unwrap_or_default()) - .collect::>(); - - // The provider ID, which we'll use to search for the provider's name. - let provider_id = scholarship.get("provider_id") - .unwrap_or(&ValueType::String(None)) - .to_string(); - - // This is a mess, we'll clean it up later. - let parsed_relations = relations - .iter() - .filter_map(|relation| { - db_relations.iter().find(|search| { - search.id == *relation - }) - .and_then(|search| Some(search.display_text.clone())) - }) - .collect::>(); - - ScholarshipInfo { - name, - provider_name: provider_id, - relations: parsed_relations, - } - }) - .collect::>(); - - // TODO Convert into CSV and send to client. For now I think I'm just going to serialize to json and print it. - leptos::logging::log!("Scholarships list: {:?}", resolved); - - Ok(Vec::new()) -} - #[component] pub fn AdminUtilsPage() -> impl IntoView { let create_comparisons = ServerAction::::new(); let create_dates = ServerAction::::new(); - let create_export = ServerAction::::new(); + let create_export = ServerAction::::new(); let on_click_comparisons = move |_| { - create_comparisons.dispatch(CreateTestComparisons{}); + create_comparisons.dispatch(CreateTestComparisons {}); }; let on_click_dates = move |_| { - create_dates.dispatch(CreateDates{dates: get_important_dates()}); + create_dates.dispatch(CreateDates { + dates: get_important_dates(), + }); }; let on_click_export = move |_| { - create_export.dispatch(CreateScholarshipExport{}); + spawn_local(async move { + let file_bytes = get_scholarship_csv().await.unwrap_or_default(); + + let uint_arr = js_sys::Uint8Array::new_with_length(file_bytes.len() as u32); + uint_arr.copy_from(&file_bytes); + + let blob_parts = js_sys::Array::new(); + blob_parts.push(&uint_arr); + + let blob = Blob::new_with_u8_array_sequence(&blob_parts).unwrap(); + + let blob_url = Url::create_object_url_with_blob(&blob).unwrap(); + + let window = window(); + let document = window.document().unwrap(); + let a: HtmlAnchorElement = document.create_element("a").unwrap().dyn_into().unwrap(); + + a.set_href(&blob_url); + a.set_download("scholarships.csv"); + document.body().unwrap().append_child(&a).unwrap(); + a.click(); + + document.body().unwrap().remove_child(&a).unwrap(); + Url::revoke_object_url(&blob_url).unwrap(); + }); }; view! { diff --git a/src/pages/api/exports.rs b/src/pages/api/exports.rs new file mode 100644 index 0000000..54a39f3 --- /dev/null +++ b/src/pages/api/exports.rs @@ -0,0 +1,237 @@ +#[cfg(feature = "ssr")] +mod imports { + pub use super::super::{COMPARISONS_TABLE, PROVIDER_CONTACT_TABLE, SCHOLARSHIPS_TABLE}; + pub use crate::common::ValueType; + pub use crate::utils::server::create_dynamo_client; + pub use aws_sdk_dynamodb::Client; + pub use aws_sdk_dynamodb::error::ProvideErrorMetadata; + pub use indexmap::IndexMap; + pub use std::collections::HashMap; +} + +use crate::common::ExpandableInfo; +use leptos::prelude::*; + +#[server] +pub async fn get_scholarship_csv() -> Result, ServerFnError> { + use imports::*; + use leptos::logging::debug_log; + + #[derive(serde::Serialize)] + struct ResolvedScholarshipInfo { + contact_email: String, + first_name: String, + last_name: String, + scholarship_name: String, + sports_requirements: String, + gpa_requirements: String, + major_requirements: String, + additional_requirements: String, + community_requirements: String, + residency_requirements: String, + } + + // Get the scholarships and comparisons + let client = create_dynamo_client().await; + + let scan_table = async |client: &Client, table_name| { + client + .scan() + .table_name(table_name) + .send() + .await + .map(|output| output.items) + .ok() + .flatten() + .unwrap_or_default() + .into_iter() + .map(|item| { + serde_dynamo::from_item(item).unwrap_or(ExpandableInfo::new("".to_string())) + // item.iter() + // .map(|(k, v)| (k.to_owned(), ValueType::from(v))) + // .collect::>() + }) + .collect::>() + }; + + let scan_table_hash_map = async |client: &Client, table_name| { + client + .scan() + .table_name(table_name) + .send() + .await + .map(|output| output.items) + .ok() + .flatten() + .unwrap_or_default() + .into_iter() + .map(|item| { + // serde_dynamo::from_item(item).unwrap_or(ExpandableInfo::new("".to_string())) + item.iter() + .map(|(k, v)| (k.to_owned(), ValueType::from(v))) + .collect::>() + }) + .collect::>>() + }; + + let master_scholarships = scan_table(&client, SCHOLARSHIPS_TABLE).await; + let master_contacts = scan_table_hash_map(&client, PROVIDER_CONTACT_TABLE).await; + let master_relations = scan_table_hash_map(&client, COMPARISONS_TABLE).await; + + // We now want to map the relations to the scholarships, as well as the contacts. + // This should take the information from the current scholarship, then relate the IDs of the + // requirements in the scholarship to the IDs of the relations in the master list. + let resolved_scholarships = master_scholarships + .into_iter() + .map(|scholarship| { + // debug_log!("Scholarship: {:?}", scholarship); + // We want the requirements IDs and the provider ID. + let requirements_map = scholarship + .data + .get("requirements") + .unwrap_or(&ValueType::Map(None)) + .as_map() + .ok() + .flatten() + .unwrap_or_default(); + + let provider_id = scholarship + .data + .get("provider_id") + .map(|v| v.as_string().ok().flatten().unwrap_or_default()) + .unwrap_or_default(); + + // Search for the provider ID in the master list. + let contact_info = master_contacts + .iter() + .find(|contact| { + let subject = contact + .get("subject") + .map(|v| v.as_string().ok().flatten().unwrap_or_default()) + .unwrap_or_default(); + + debug_log!("Comparing {:?} and {:?}", subject, provider_id); + + subject == provider_id + }) + .cloned() + .unwrap_or_default(); + + // Resolve the requirements - this should resolve as another category-separated map, + // but with requirement display text instead of IDs. + let resolved_requirements = requirements_map + .iter() + .map(|(category, selected_val)| { + let selected_list = selected_val.as_list().ok().flatten().unwrap_or_default(); + + let resolved_names = selected_list + .into_iter() + .map(|id_val| { + let id = id_val.as_string().ok().flatten().unwrap_or_default(); + + let resolved_relation = master_relations + .iter() + .find(|item| { + let comp_id = item + .get("id") + .map(|v| v.as_string().ok().flatten().unwrap_or_default()) + .unwrap_or_default(); + + comp_id == id + }) + .cloned() + .map(|v| { + v.get("display_text").map(|text_opt| { + text_opt.as_string().ok().flatten().unwrap_or_default() + }) + }) + .flatten() + .unwrap_or_default(); + + resolved_relation + }) + .collect::>(); + + (category.clone(), resolved_names) + }) + .collect::>>(); + debug_log!("Resolved requirements: {:?}", resolved_requirements); + + let contact_email = contact_info + .get("contact_email") + .map(|v| v.as_string().ok().flatten().unwrap_or_default()) + .unwrap_or_default(); + + let first_name = contact_info + .get("first_name") + .map(|v| v.as_string().ok().flatten().unwrap_or_default()) + .unwrap_or_default(); + + let last_name = contact_info + .get("last_name") + .map(|v| v.as_string().ok().flatten().unwrap_or_default()) + .unwrap_or_default(); + + let scholarship_name = scholarship + .data + .get("name") + .map(|v| v.as_string().ok().flatten().unwrap_or_default()) + .unwrap_or_default(); + + debug_log!( + "{:?}, {:?}, {:?}, {:?}", + scholarship_name, + contact_email, + first_name, + last_name + ); + + // We want to create some structure that's got the correct information. Specifically, + // we only need the provider contact info, the scholarship's name, and the requirements + // that they chose in plain text. + ResolvedScholarshipInfo { + first_name, + last_name, + contact_email, + scholarship_name, + sports_requirements: resolved_requirements + .get("sports_participation") + .map(|list| list.join("; ")) + .unwrap_or_default(), + gpa_requirements: resolved_requirements + .get("gpa") + .map(|list| list.join("; ")) + .unwrap_or_default(), + major_requirements: resolved_requirements + .get("major") + .map(|list| list.join("; ")) + .unwrap_or_default(), + additional_requirements: resolved_requirements + .get("misc") + .map(|list| list.join("; ")) + .unwrap_or_default(), + community_requirements: resolved_requirements + .get("community_service") + .map(|list| list.join("; ")) + .unwrap_or_default(), + residency_requirements: resolved_requirements + .get("residency") + .map(|list| list.join("; ")) + .unwrap_or_default(), + } + }) + .collect::>(); + + // Now that we have a series of scholarships, we need to serialize them into CSV and send the + // file to the client. + let mut writer = csv::Writer::from_writer(Vec::new()); + resolved_scholarships.iter().for_each(|scholarship| { + _ = writer.serialize(scholarship); + }); + writer.flush()?; + + let result = writer.into_inner()?; + debug_log!("{:?}", String::from_utf8(result.clone())); + + Ok(result) +} diff --git a/src/pages/api/mod.rs b/src/pages/api/mod.rs index fb8c5e8..ae7606f 100644 --- a/src/pages/api/mod.rs +++ b/src/pages/api/mod.rs @@ -1,15 +1,16 @@ -mod comparisons; -mod scholarships; -mod dates; mod admin; +mod comparisons; +mod dates; +pub mod exports; mod providers; +mod scholarships; pub mod students; -pub use comparisons::*; -pub use scholarships::*; -pub use dates::*; pub use admin::*; +pub use comparisons::*; +pub use dates::*; pub use providers::*; +pub use scholarships::*; #[cfg(feature = "ssr")] pub static COMPARISONS_TABLE: &str = "leptos-comparisons"; From 64554d81bcf1437f380436bca379f42118e6cadd Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Fri, 27 Feb 2026 14:52:54 -0500 Subject: [PATCH 08/21] feat: begin implementation of file drop component --- Cargo.lock | 845 +++++++++++++++++++++------------- Cargo.toml | 20 +- src/components/file_drop.rs | 53 +++ src/components/mod.rs | 7 +- src/pages/admin/utils_page.rs | 3 +- 5 files changed, 586 insertions(+), 342 deletions(-) create mode 100644 src/components/file_drop.rs diff --git a/Cargo.lock b/Cargo.lock index f753494..8dde044 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,16 +27,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d" dependencies = [ "futures", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "wasm-bindgen-futures", ] [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "async-lock" @@ -110,9 +110,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.12" +version = "1.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96571e6996817bf3d58f6b569e4b9fd2e9d2fcf9f7424eed07b2ce9bb87535e5" +checksum = "8a8fc176d53d6fe85017f230405e3255cedb4a02221cb55ed6d76dccbbb099b2" dependencies = [ "aws-credential-types", "aws-runtime", @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.11" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" +checksum = "6d203b0bf2626dcba8665f5cd0871d7c2c0930223d6b6be9097592fea21242d0" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.15.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.35.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -174,9 +174,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.17" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81b5b2898f6798ad58f484856768bca817e3cd9de0974c24ae0f1113fe88f1b" +checksum = "ede2ddc593e6c8acc6ce3358c28d6677a6dc49b65ba4b37a2befe14a11297e75" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -187,9 +187,10 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", + "bytes-utils", "fastrand", - "http 0.2.12", - "http-body 0.4.6", + "http 1.4.0", + "http-body 1.0.1", "percent-encoding", "pin-project-lite", "tracing", @@ -198,15 +199,16 @@ dependencies = [ [[package]] name = "aws-sdk-dynamodb" -version = "1.101.0" +version = "1.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f98cd9e5f2fc790aff1f393bc3c8680deea31c05d3c6f23b625cdc50b1b6b4" +checksum = "561bf86e858a2759c6876b517b13f3f4051a6484abbb0d8a1f4dfc5d902cc85a" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -214,21 +216,23 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sso" -version = "1.91.0" +version = "1.95.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee6402a36f27b52fe67661c6732d684b2635152b676aa2babbfb5204f99115d" +checksum = "00c5ff27c6ba2cbd95e6e26e2e736676fdf6bcf96495b187733f521cfe4ce448" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -236,21 +240,23 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.93.0" +version = "1.97.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45a7f750bbd170ee3677671ad782d90b894548f4e4ae168302c57ec9de5cb3e" +checksum = "4d186f1e5a3694a188e5a0640b3115ccc6e084d104e16fd6ba968dca072ffef8" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -258,21 +264,23 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.95.0" +version = "1.99.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55542378e419558e6b1f398ca70adb0b2088077e79ad9f14eb09441f2f7b2164" +checksum = "9acba7c62f3d4e2408fa998a3a8caacd8b9a5b5549cf36e2372fbdae329d5449" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", "aws-smithy-http", "aws-smithy-json", + "aws-smithy-observability", "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -281,15 +289,16 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", + "http 1.4.0", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.3.7" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e523e1c4e8e7e8ff219d732988e22bfeae8a1cafdbe6d9eca1546fa080be7c" +checksum = "37411f8e0f4bea0c3ca0958ce7f18f6439db24d555dbd809787262cd00926aa9" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -309,9 +318,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.7" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee19095c7c4dda59f1697d028ce704c24b2d33c6718790c7f1d5a3015b4107c" +checksum = "5cc50d0f63e714784b84223abd7abbc8577de8c35d699e0edd19f0a88a08ae13" dependencies = [ "futures-util", "pin-project-lite", @@ -320,9 +329,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.62.6" +version = "0.63.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" +checksum = "d619373d490ad70966994801bc126846afaa0d1ee920697a031f0cf63f2568e7" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", @@ -330,9 +339,9 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 0.2.12", "http 1.4.0", - "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", "percent-encoding", "pin-project-lite", "pin-utils", @@ -341,9 +350,9 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.1.5" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e62db736db19c488966c8d787f52e6270be565727236fd5579eaa301e7bc4a" +checksum = "00ccbb08c10f6bcf912f398188e42ee2eab5f1767ce215a02a73bc5df1bbdd95" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -360,7 +369,7 @@ dependencies = [ "hyper-util", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -371,27 +380,27 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.61.9" +version = "0.62.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" +checksum = "27b3a779093e18cad88bbae08dc4261e1d95018c4c5b9356a52bcae7c0b6e9bb" dependencies = [ "aws-smithy-types", ] [[package]] name = "aws-smithy-observability" -version = "0.1.5" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f616c3f2260612fe44cede278bafa18e73e6479c4e393e2c4518cf2a9a228a" +checksum = "4d3f39d5bb871aaf461d59144557f16d5927a5248a983a40654d9cf3b9ba183b" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.9" +version = "0.60.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae5d689cf437eae90460e944a58b5668530d433b4ff85789e69d2f2a556e057d" +checksum = "05f76a580e3d8f8961e5d48763214025a2af65c2fa4cd1fb7f270a0e107a71b0" dependencies = [ "aws-smithy-types", "urlencoding", @@ -399,9 +408,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.9.5" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a392db6c583ea4a912538afb86b7be7c5d8887d91604f50eb55c262ee1b4a5f5" +checksum = "22ccf7f6eba8b2dcf8ce9b74806c6c185659c311665c4bf8d6e71ebd454db6bf" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -415,6 +424,7 @@ dependencies = [ "http 1.4.0", "http-body 0.4.6", "http-body 1.0.1", + "http-body-util", "pin-project-lite", "pin-utils", "tokio", @@ -423,9 +433,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.9.3" +version = "1.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0d43d899f9e508300e587bf582ba54c27a452dd0a9ea294690669138ae14a2" +checksum = "b4af6e5def28be846479bbeac55aa4603d6f7986fc5da4601ba324dd5d377516" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -440,9 +450,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.5" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905cb13a9895626d49cf2ced759b062d913834c7482c38e49557eac4e6193f01" +checksum = "8ca2734c16913a45343b37313605d84e7d8b34a4611598ce1d25b35860a2bed3" dependencies = [ "base64-simd", "bytes", @@ -466,18 +476,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.13" +version = "0.60.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" +checksum = "b53543b4b86ed43f051644f704a98c7291b3618b67adf057ee77a366fa52fcaa" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.11" +version = "1.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164" +checksum = "0470cc047657c6e286346bdf10a8719d26efd6a91626992e0e64481e44323e96" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -573,9 +583,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -588,9 +598,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.8.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234655ec178edd82b891e262ea7cf71f6584bcd09eff94db786be23f1821825c" +checksum = "2d13a61f2963b88eef9c1be03df65d42f6996dfeac1054870d950fcf66686f83" dependencies = [ "bon-macros", "rustversion", @@ -598,9 +608,9 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.8.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ec27229c38ed0eb3c0feee3d2c1d6a4379ae44f418a29a658890e062d8f365" +checksum = "d314cc62af2b6b0c65780555abb4d02a03dd3b799cd42419044f0c38d99738c0" dependencies = [ "darling 0.23.0", "ident_case", @@ -613,15 +623,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bytes-utils" @@ -641,9 +651,9 @@ checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -659,9 +669,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -688,7 +698,7 @@ checksum = "a9dbbdc4b4d349732bc6690de10a9de952bd39ba6a065c586e26600b6b0b91f5" dependencies = [ "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -731,9 +741,9 @@ dependencies = [ [[package]] name = "const-str" -version = "0.7.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0664d2867b4a32697dfe655557f5c3b187e9b605b38612a748e5ec99811d160" +checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f" [[package]] name = "const_format" @@ -781,13 +791,33 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case_extras" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c70f0faf8aa9d17787557d5eae854d7755cac50f5c3d12c81d3d57661cebb" +dependencies = [ + "convert_case 0.11.0", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -935,9 +965,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "default-struct-builder" @@ -953,9 +983,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -1081,9 +1111,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fnv" @@ -1091,6 +1121,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1108,9 +1144,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1123,9 +1159,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1133,33 +1169,32 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -1168,21 +1203,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1192,7 +1227,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1208,9 +1242,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1224,12 +1258,25 @@ name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", "wasip2", + "wasip3", "wasm-bindgen", ] @@ -1254,6 +1301,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gloo-utils" version = "0.2.0" @@ -1317,6 +1376,15 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -1324,10 +1392,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] -name = "hermit-abi" -version = "0.5.2" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -1513,7 +1581,7 @@ dependencies = [ "http 1.4.0", "hyper 1.8.1", "hyper-util", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -1523,14 +1591,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -1539,7 +1606,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "tokio", "tower-service", "tracing", @@ -1547,9 +1614,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1855,6 +1922,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1902,9 +1975,9 @@ checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" [[package]] name = "inventory" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +checksum = "009ae045c87e7082cb72dab0ccd01ae075dd00141ddc108f43a0ea150a9e7227" dependencies = [ "rustversion", ] @@ -1952,9 +2025,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "14dc6f6450b3f6d4ed5b16327f38fed626d375a886159ca555bd7822c0c3a5a6" dependencies = [ "once_cell", "wasm-bindgen", @@ -1980,17 +2053,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "leptos" -version = "0.8.15" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9569fc37575a5d64c0512145af7630bf651007237ef67a8a77328199d315bb" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "leptos" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ad8042559850ee5253293b8460a75513f7d542021b9303083d5c236bcdd16f" dependencies = [ "any_spawner", "base64 0.22.1", "cfg-if", "either_of", "futures", - "getrandom 0.3.4", + "getrandom 0.4.1", "hydration_context", "leptos_config", "leptos_dom", @@ -2011,10 +2090,10 @@ dependencies = [ "server_fn", "slotmap", "tachys", - "thiserror 2.0.17", + "thiserror 2.0.18", "throw_error", - "typed-builder 0.23.2", - "typed-builder-macro 0.23.2", + "typed-builder", + "typed-builder-macro", "wasm-bindgen", "wasm-bindgen-futures", "wasm_split_helpers", @@ -2035,7 +2114,32 @@ dependencies = [ "leptos", "paste", "send_wrapper", - "thiserror 2.0.17", + "thiserror 2.0.18", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos-use" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cded1fffd0646ff028f38552724be36054052a35d293531c8766bbeb25dcc5" +dependencies = [ + "cfg-if", + "chrono", + "codee", + "cookie", + "default-struct-builder", + "futures-util", + "gloo-timers", + "js-sys", + "lazy_static", + "leptos", + "paste", + "send_wrapper", + "thiserror 2.0.18", + "unic-langid", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2043,16 +2147,16 @@ dependencies = [ [[package]] name = "leptos_animate" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6225b975174171dff7b70563d08901fa7ec8d794615e6c8d6f14b23a613c40" +checksum = "e806c5ae7732fc61e08f6e4e14ec0f5e48034cf9d500c6e48548dc3c44b41424" dependencies = [ "anyhow", "bon", "futures", "leptos", "paste", - "reactive_stores 0.2.5", + "reactive_stores", "reactive_stores_macro", "send_wrapper", "wasm-bindgen", @@ -2062,13 +2166,12 @@ dependencies = [ [[package]] name = "leptos_axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0caa95760f87f3067e05025140becefdbdfd36cbc2adac4519f06e1f1edf4af" +checksum = "196de3f5cde6a4c4cd254bb16dc6abd2efbf46cc3ae1b6c7da0731f77b4bdf61" dependencies = [ "any_spawner", "axum", - "dashmap", "futures", "hydration_context", "leptos", @@ -2076,7 +2179,7 @@ dependencies = [ "leptos_macro", "leptos_meta", "leptos_router", - "parking_lot", + "or_poisoned", "server_fn", "tachys", "tokio", @@ -2086,22 +2189,22 @@ dependencies = [ [[package]] name = "leptos_config" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071fc40aeb9fcab885965bad1887990477253ad51f926cd19068f45a44c59e89" +checksum = "19a2ac32008dda0d657f2147cc33336f4e743e091597db10f7a99d668e92a46d" dependencies = [ "config", "regex", "serde", - "thiserror 2.0.17", - "typed-builder 0.21.2", + "thiserror 2.0.18", + "typed-builder", ] [[package]] name = "leptos_dom" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f4330c88694c5575e0bfe4eecf81b045d14e76a4f8b00d5fd2a63f8779f895" +checksum = "35742e9ed8f8aaf9e549b454c68a7ac0992536e06856365639b111f72ab07884" dependencies = [ "js-sys", "or_poisoned", @@ -2114,14 +2217,14 @@ dependencies = [ [[package]] name = "leptos_hot_reload" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d61ec3e1ff8aaee8c5151688550c0363f85bc37845450764c31ff7584a33f38" +checksum = "9d2a0f220c8a5ef3c51199dfb9cdd702bc0eb80d52fbe70c7890adfaaae8a4b1" dependencies = [ "anyhow", "camino", "indexmap", - "parking_lot", + "or_poisoned", "proc-macro2", "quote", "rstml", @@ -2142,9 +2245,9 @@ dependencies = [ [[package]] name = "leptos_integration_utils" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13cccc9305df53757bae61bf15641bfa6a667b5f78456ace4879dfe0591ae0e8" +checksum = "c097f89cd9aa606297672f56fa5bdda09f01609a9f4eefaccdbb5ab5afea4279" dependencies = [ "futures", "hydration_context", @@ -2157,13 +2260,14 @@ dependencies = [ [[package]] name = "leptos_macro" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86ffd2e9cf3e264e9b3e16bdb086cefa26bd0fa7bc6a26b0cc5f6c1fd3178ed" +checksum = "712325a77f1d050bf2897061ccaf2b075930aab36954980d658f04452686c474" dependencies = [ "attribute-derive", "cfg-if", - "convert_case 0.10.0", + "convert_case 0.11.0", + "convert_case_extras", "html-escape", "itertools", "leptos_hot_reload", @@ -2180,9 +2284,9 @@ dependencies = [ [[package]] name = "leptos_meta" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d489e38d3f541e9e43ecc2e3a815527840345a2afca629b3e23fcc1dd254578" +checksum = "6c3efe657b4c55ed2e078922786ffe20acfb71767c3dd913767b09a35c75c890" dependencies = [ "futures", "indexmap", @@ -2203,21 +2307,21 @@ dependencies = [ "codee", "jsonwebtoken", "leptos", - "leptos-use", + "leptos-use 0.16.3", "leptos_router", "oauth2", "reqwest", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] [[package]] name = "leptos_router" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e573711f2fb9ab5d655ec38115220d359eaaf1dcb93cc0ea624543b6dba959" +checksum = "35058d4096407b8369843b5b5d227588dfd57ecc9e9bda0567523f084dce69e8" dependencies = [ "any_spawner", "either_of", @@ -2232,7 +2336,7 @@ dependencies = [ "rustc_version", "send_wrapper", "tachys", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "wasm-bindgen", "web-sys", @@ -2252,9 +2356,9 @@ dependencies = [ [[package]] name = "leptos_server" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf1045af93050bf3388d1c138426393fc131f6d9e46a65519da884c033ed730" +checksum = "da974775c5ccbb6bd64be7f53f75e8321542e28f21563a416574dbe4d5447eae" dependencies = [ "any_spawner", "base64 0.22.1", @@ -2272,15 +2376,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "linear-map" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "litemap" @@ -2343,9 +2441,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -2399,9 +2497,9 @@ checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -2421,16 +2519,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "oauth2" version = "5.0.0" @@ -2439,7 +2527,7 @@ checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ "base64 0.22.1", "chrono", - "getrandom 0.2.16", + "getrandom 0.2.17", "http 1.4.0", "rand 0.8.5", "serde", @@ -2457,7 +2545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d" dependencies = [ "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2468,9 +2556,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "or_poisoned" @@ -2490,16 +2578,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - [[package]] name = "parking_lot_core" version = "0.9.12" @@ -2632,9 +2710,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -2654,9 +2732,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2707,7 +2785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2727,7 +2805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2736,23 +2814,23 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] [[package]] name = "reactive_graph" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f0df355582937223ea403e52490201d65295bd6981383c69bfae5a1f8730c2" +checksum = "35774620b3da884a07341e9e36612e1509b1eb0553ef3bb76f1547dd1b797417" dependencies = [ "any_spawner", "async-lock", @@ -2768,27 +2846,10 @@ dependencies = [ "send_wrapper", "serde", "slotmap", - "thiserror 2.0.17", + "thiserror 2.0.18", "web-sys", ] -[[package]] -name = "reactive_stores" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79983e88dfd1a2925e29a4853ab9161b234ea78dd0d44ed33a706c9cd5e35762" -dependencies = [ - "dashmap", - "guardian", - "itertools", - "or_poisoned", - "paste", - "reactive_graph", - "reactive_stores_macro", - "rustc-hash", - "send_wrapper", -] - [[package]] name = "reactive_stores" version = "0.3.1" @@ -2830,9 +2891,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2842,9 +2903,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2853,15 +2914,15 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -2903,7 +2964,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -2921,7 +2982,7 @@ dependencies = [ "quote", "syn", "syn_derive", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2953,14 +3014,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "once_cell", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.9", "subtle", "zeroize", ] @@ -2979,9 +3040,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] @@ -2998,9 +3059,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -3016,9 +3077,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -3052,6 +3113,7 @@ dependencies = [ "icondata", "indexmap", "leptos", + "leptos-use 0.18.3", "leptos_animate", "leptos_axum", "leptos_icons", @@ -3085,9 +3147,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation", @@ -3098,9 +3160,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3195,7 +3257,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3221,16 +3283,15 @@ dependencies = [ [[package]] name = "server_fn" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353d02fa2886cd8dae0b8da0965289fa8f2ecc7df633d1ce965f62fdf9644d29" +checksum = "0fafe3a832a747ded8dc4827c538a2a064284f150c1c44c51ec56b58bd947dd7" dependencies = [ "axum", "base64 0.22.1", "bytes", "const-str", "const_format", - "dashmap", "futures", "gloo-net", "http 1.4.0", @@ -3238,6 +3299,7 @@ dependencies = [ "hyper 1.8.1", "inventory", "js-sys", + "or_poisoned", "pin-project-lite", "rustc_version", "rustversion", @@ -3246,7 +3308,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror 2.0.17", + "thiserror 2.0.18", "throw_error", "tokio", "tower", @@ -3261,12 +3323,12 @@ dependencies = [ [[package]] name = "server_fn_macro" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950b8cfc9ff5f39ca879c5a7c5e640de2695a199e18e424c3289d0964cabe642" +checksum = "14faf423aab09f8c3eb2d9785bb37f11a255cdf01857d3c6083eacc82269c191" dependencies = [ "const_format", - "convert_case 0.8.0", + "convert_case 0.11.0", "proc-macro2", "quote", "rustc_version", @@ -3324,9 +3386,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slotmap" @@ -3355,9 +3417,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -3389,9 +3451,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3432,9 +3494,9 @@ dependencies = [ [[package]] name = "tachys" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b2db11e455f7e84e2cc3e76f8a3f3843f7956096265d5ecff781eabe235077" +checksum = "14691ce610071757bd17bd8d572065192c9c93f9f169125390aaea345a4c56b9" dependencies = [ "any_spawner", "async-trait", @@ -3447,14 +3509,12 @@ dependencies = [ "indexmap", "itertools", "js-sys", - "linear-map", "next_tuple", "oco_ref", "or_poisoned", - "parking_lot", "paste", "reactive_graph", - "reactive_stores 0.3.1", + "reactive_stores", "rustc-hash", "rustc_version", "send_wrapper", @@ -3475,11 +3535,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -3495,9 +3555,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -3515,29 +3575,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -3550,6 +3611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -3564,7 +3626,7 @@ dependencies = [ "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3596,7 +3658,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.36", + "rustls 0.23.37", "tokio", ] @@ -3627,9 +3689,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "serde_core", "serde_spanned", @@ -3649,18 +3711,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -3767,37 +3829,17 @@ dependencies = [ "log", "rand 0.9.2", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "utf-8", ] -[[package]] -name = "typed-builder" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" -dependencies = [ - "typed-builder-macro 0.21.2", -] - [[package]] name = "typed-builder" version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda" dependencies = [ - "typed-builder-macro 0.23.2", -] - -[[package]] -name = "typed-builder-macro" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "typed-builder-macro", ] [[package]] @@ -3817,6 +3859,24 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "unic-langid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" +dependencies = [ + "tinystr", +] + [[package]] name = "unicase" version = "2.9.0" @@ -3825,9 +3885,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -3886,11 +3946,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -3935,18 +3995,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "60722a937f594b7fde9adb894d7c092fc1bb6612897c46368d18e7a20208eff2" dependencies = [ "cfg-if", "once_cell", @@ -3957,11 +4026,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "8a89f4650b770e4521aa6573724e2aed4704372151bd0de9d16a3bbabb87441a" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -3970,9 +4040,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "0fac8c6395094b6b91c4af293f4c79371c163f9a6f56184d2c9a85f5a95f3950" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3980,9 +4050,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "ab3fabce6159dc20728033842636887e4877688ae94382766e00b180abac9d60" dependencies = [ "bumpalo", "proc-macro2", @@ -3993,18 +4063,40 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "de0e091bdb824da87dc01d967388880d017a0a9bc4f3bdc0d86ee9f9336e3bb5" dependencies = [ "unicode-ident", ] [[package]] -name = "wasm-streams" -version = "0.4.2" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -4036,10 +4128,22 @@ dependencies = [ ] [[package]] -name = "web-sys" -version = "0.3.83" +name = "wasmparser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "705eceb4ce901230f8625bd1d665128056ccbe4b7408faa625eec1ba80f59a97" dependencies = [ "js-sys", "wasm-bindgen", @@ -4280,9 +4384,91 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -4333,18 +4519,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", @@ -4395,6 +4581,7 @@ version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -4413,6 +4600,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 401029d..e1b3750 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,16 +14,16 @@ console_error_panic_hook = { version = "0.1", optional = true} leptos_axum = { version = "0.8", optional = true } leptos_meta = { version = "0.8" } tokio = { version = "1", features = ["rt-multi-thread"], optional = true } -wasm-bindgen = { version = "=0.2.106", optional = true } -aws-sdk-dynamodb = { version = "1", optional = true } -aws-config = { version = "1.6.2", optional = true } -serde = "1.0.219" -serde_dynamo = { version = "4.2.14", features = ["aws-sdk-dynamodb+1"], optional = true } -uuid = { version = "1.17.0", features = ["serde", "v4", "js"] } +wasm-bindgen = { version = "0.2", optional = true } +aws-sdk-dynamodb = { version = "^1", optional = true } +aws-config = { version = "^1.8", optional = true } +serde = "^1.0" +serde_dynamo = { version = "^4", features = ["aws-sdk-dynamodb+1"], optional = true } +uuid = { version = "^1", features = ["serde", "v4", "js"] } leptos_oidc = "0.9.0" -base64 = "0.22.1" -chrono = "0.4.42" -leptos_animate = "0.1.9" +base64 = "0.22" +chrono = "0.4" +leptos_animate = "0.1" macros = { path = "./macros" } traits = { path = "./traits" } @@ -31,6 +31,7 @@ icondata = { version = "0.7.0", features = ["font-awesome"] } leptos_icons = "0.7.1" indexmap = { version = "2.13.0", features = ["serde"] } csv = "1.4.0" +leptos-use = { version = "0.18.3", features = ["use_drop_zone"] } [features] default = ["ssr"] @@ -49,6 +50,7 @@ ssr = [ "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", + "leptos-use/ssr" ] # Defines a size-optimized profile for the WASM bundle in release mode diff --git a/src/components/file_drop.rs b/src/components/file_drop.rs new file mode 100644 index 0000000..b3040b1 --- /dev/null +++ b/src/components/file_drop.rs @@ -0,0 +1,53 @@ +use leptos::html::Div; +use leptos::logging::debug_log; +use leptos::prelude::*; +use leptos_use::{UseDropZoneOptions, UseDropZoneReturn, use_drop_zone_with_options}; + +#[component] +pub fn FileDrop() -> impl IntoView { + // I'm really not sure how to hydrate this, but we'll try our best to do it somehow. + // We just need to know the file names and the size, not actually have the file itself. + + let zone_ref = NodeRef::
::new(); + + let file_list = RwSignal::new(Vec::new()); + + // Files are just the current files that are being processed, not the full list of files that + // have been dropped. So, if the user drops multiple files, it will be the list of those files, + // but it will wipe the previous set of dropped files. + let UseDropZoneReturn { + is_over_drop_zone, + files, + } = use_drop_zone_with_options( + zone_ref, + UseDropZoneOptions::default() + .on_over(|_| debug_log!("File is over the drop zone!")) + .on_drop(move |mut ev| { + file_list.update(|list| { + list.extend(ev.files); + }); + }), + ); + + Effect::new(move || { + let file_names = file_list + .get() + .iter() + .map(|file| file.name()) + .collect::>() + .join(", "); + + debug_log!("Updated file list. Current file names: {}", file_names); + }); + + view! { +
+ +
"File name: "{file.name()}
+
+ } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index 106d43a..30a6c72 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -3,6 +3,7 @@ mod banner; mod dashboard_button; mod data_display; mod date; +mod file_drop; mod header; mod lists; mod loading; @@ -17,7 +18,7 @@ mod utils; mod validated_form; pub use self::{ - action_button::*, banner::*, dashboard_button::*, data_display::*, date::*, header::*, - lists::*, loading::*, multi_entry::*, panel::*, row::*, tabs::*, text_field::*, toasts::*, - validated_form::*, + action_button::*, banner::*, dashboard_button::*, data_display::*, date::*, file_drop::*, + header::*, lists::*, loading::*, multi_entry::*, panel::*, row::*, tabs::*, text_field::*, + toasts::*, validated_form::*, }; diff --git a/src/pages/admin/utils_page.rs b/src/pages/admin/utils_page.rs index 202f770..861dc36 100644 --- a/src/pages/admin/utils_page.rs +++ b/src/pages/admin/utils_page.rs @@ -1,5 +1,5 @@ use crate::common::{DateInfo, DateRange}; -use crate::components::ActionButton; +use crate::components::{ActionButton, FileDrop}; use crate::pages::api::exports::get_scholarship_csv; use crate::pages::api::{CreateDates, CreateTestComparisons, exports::GetScholarshipCsv}; use chrono::DateTime; @@ -109,6 +109,7 @@ pub fn AdminUtilsPage() -> impl IntoView { "Create Comparisons" "Create Dates" "Scholarship Export" +
}.into_any() From 40d1ef2a5f4dd58ebbc297de98b175b32c272dbb Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Sun, 1 Mar 2026 12:17:20 -0500 Subject: [PATCH 09/21] feat: implement file upload functionality --- Cargo.lock | 731 ++++++++++++++++++++++++++- Cargo.toml | 7 +- src/common/user.rs | 2 + src/components/file_drop.rs | 130 +++-- src/pages/admin/utils_page.rs | 3 +- src/pages/api/files.rs | 60 +++ src/pages/api/mod.rs | 2 + src/pages/api/tokens.rs | 50 ++ src/pages/mod.rs | 2 +- src/pages/student/additional_page.rs | 3 + 10 files changed, 948 insertions(+), 42 deletions(-) create mode 100644 src/pages/api/files.rs create mode 100644 src/pages/api/tokens.rs diff --git a/Cargo.lock b/Cargo.lock index 8dde044..93a5549 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -157,6 +163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] @@ -181,6 +188,7 @@ dependencies = [ "aws-credential-types", "aws-sigv4", "aws-smithy-async", + "aws-smithy-eventstream", "aws-smithy-http", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -189,7 +197,9 @@ dependencies = [ "bytes", "bytes-utils", "fastrand", + "http 0.2.12", "http 1.4.0", + "http-body 0.4.6", "http-body 1.0.1", "percent-encoding", "pin-project-lite", @@ -221,6 +231,41 @@ dependencies = [ "tracing", ] +[[package]] +name = "aws-sdk-s3" +version = "1.124.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744c09d75dfec039a05cf8e117c995ded3b0baffa6eb83f3ed7075a01d8d8947" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "http-body 1.0.1", + "lru", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] + [[package]] name = "aws-sdk-sso" version = "1.95.0" @@ -301,19 +346,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37411f8e0f4bea0c3ca0958ce7f18f6439db24d555dbd809787262cd00926aa9" dependencies = [ "aws-credential-types", + "aws-smithy-eventstream", "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", + "crypto-bigint 0.5.5", "form_urlencoded", "hex", "hmac", "http 0.2.12", "http 1.4.0", + "p256", "percent-encoding", + "ring", "sha2", + "subtle", "time", "tracing", + "zeroize", ] [[package]] @@ -327,12 +378,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "aws-smithy-checksums" +version = "0.64.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180dddf5ef0f52a2f99e2fada10e16ea610e507ef6148a42bdc4d5867596aa00" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c0b3e587fbaa5d7f7e870544508af8ce82ea47cd30376e69e1e37c4ac746f79" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + [[package]] name = "aws-smithy-http" version = "0.63.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d619373d490ad70966994801bc126846afaa0d1ee920697a031f0cf63f2568e7" dependencies = [ + "aws-smithy-eventstream", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", @@ -559,6 +643,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.21.7" @@ -581,6 +671,12 @@ dependencies = [ "vsimd", ] +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bitflags" version = "2.11.0" @@ -661,12 +757,24 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.44" @@ -707,6 +815,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -739,6 +857,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-str" version = "1.1.0" @@ -818,6 +942,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -843,12 +977,70 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc-fast" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd92aca2c6001b1bf5ba0ff84ee74ec8501b52bbef0cac80bf25a6c1d87a83d" +dependencies = [ + "crc", + "digest", + "rustversion", + "spin 0.10.0", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -981,6 +1173,16 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1035,6 +1237,18 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature 1.6.4", +] + [[package]] name = "either" version = "1.15.0" @@ -1051,6 +1265,26 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint 0.4.9", + "der", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1109,6 +1343,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1127,6 +1371,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1260,9 +1510,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] @@ -1326,6 +1578,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "guardian" version = "1.3.0" @@ -1382,7 +1645,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1390,6 +1653,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "heck" @@ -1607,9 +1875,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2013,6 +2283,28 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -2046,6 +2338,23 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "aws-lc-rs", + "base64 0.22.1", + "getrandom 0.2.17", + "js-sys", + "pem", + "serde", + "serde_json", + "signature 2.2.0", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2305,12 +2614,12 @@ checksum = "7797ff727c5b0b558faabe2bd784159b411803f56e384578d68471c699b85649" dependencies = [ "chrono", "codee", - "jsonwebtoken", + "jsonwebtoken 9.3.1", "leptos", "leptos-use 0.16.3", "leptos_router", "oauth2", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.18", @@ -2401,6 +2710,21 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "macros" version = "0.1.0" @@ -2439,6 +2763,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.8.0" @@ -2485,7 +2819,7 @@ dependencies = [ "httparse", "memchr", "mime", - "spin", + "spin 0.9.8", "version_check", ] @@ -2495,6 +2829,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.0" @@ -2572,6 +2916,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -2603,6 +2958,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2641,6 +3006,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -2730,6 +3105,62 @@ dependencies = [ "yansi", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.37", + "socket2 0.6.2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls 0.23.37", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.44" @@ -2956,6 +3387,57 @@ dependencies = [ "web-sys", ] +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.37", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + [[package]] name = "ring" version = "0.17.14" @@ -2966,7 +3448,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -3044,9 +3526,37 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.37", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.9", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -3054,7 +3564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3066,7 +3576,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3105,6 +3615,7 @@ version = "0.1.0" dependencies = [ "aws-config", "aws-sdk-dynamodb", + "aws-sdk-s3", "axum", "base64 0.22.1", "chrono", @@ -3112,6 +3623,7 @@ dependencies = [ "csv", "icondata", "indexmap", + "jsonwebtoken 10.3.0", "leptos", "leptos-use 0.18.3", "leptos_animate", @@ -3121,6 +3633,7 @@ dependencies = [ "leptos_oidc", "leptos_router", "macros", + "reqwest 0.13.2", "serde", "serde_dynamo", "tokio", @@ -3142,7 +3655,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", - "untrusted", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", ] [[package]] @@ -3152,7 +3679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3299,6 +3826,7 @@ dependencies = [ "hyper 1.8.1", "inventory", "js-sys", + "multer", "or_poisoned", "pin-project-lite", "rustc_version", @@ -3384,6 +3912,37 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + [[package]] name = "slab" version = "0.4.12" @@ -3431,6 +3990,22 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3492,6 +4067,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tachys" version = "0.2.13" @@ -3615,6 +4211,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.49.0" @@ -3901,6 +4512,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -4149,6 +4766,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -4199,6 +4835,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -4217,6 +4864,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -4244,6 +4900,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4277,6 +4948,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4289,6 +4966,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4301,6 +4984,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4325,6 +5014,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4337,6 +5032,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4349,6 +5050,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4361,6 +5068,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index e1b3750..2492772 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" crate-type = ["cdylib", "rlib"] [dependencies] -leptos = { version = "0.8" } +leptos = { version = "0.8", features = ["multipart"] } leptos_router = { version = "0.8" } axum = { version = "0.8", optional = true } console_error_panic_hook = { version = "0.1", optional = true} @@ -16,6 +16,7 @@ leptos_meta = { version = "0.8" } tokio = { version = "1", features = ["rt-multi-thread"], optional = true } wasm-bindgen = { version = "0.2", optional = true } aws-sdk-dynamodb = { version = "^1", optional = true } +aws-sdk-s3 = { version = "1.124.0", optional = true } aws-config = { version = "^1.8", optional = true } serde = "^1.0" serde_dynamo = { version = "^4", features = ["aws-sdk-dynamodb+1"], optional = true } @@ -32,6 +33,8 @@ leptos_icons = "0.7.1" indexmap = { version = "2.13.0", features = ["serde"] } csv = "1.4.0" leptos-use = { version = "0.18.3", features = ["use_drop_zone"] } +jsonwebtoken = { version = "10.3.0", features = ["aws_lc_rs"], optional = true } +reqwest = { version = "0.13.2", features = ["json"] } [features] default = ["ssr"] @@ -45,8 +48,10 @@ ssr = [ "dep:tokio", "dep:leptos_axum", "dep:aws-sdk-dynamodb", + "dep:aws-sdk-s3", "dep:aws-config", "dep:serde_dynamo", + "dep:jsonwebtoken", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", diff --git a/src/common/user.rs b/src/common/user.rs index 49a99bf..496037a 100644 --- a/src/common/user.rs +++ b/src/common/user.rs @@ -22,4 +22,6 @@ pub struct UserClaims { /// The username of the user. This should be their email address. #[serde(rename = "username")] pub username: String, + /// The intended use for the token. + pub token_use: String, } diff --git a/src/components/file_drop.rs b/src/components/file_drop.rs index b3040b1..a509361 100644 --- a/src/components/file_drop.rs +++ b/src/components/file_drop.rs @@ -1,53 +1,125 @@ -use leptos::html::Div; +use leptos::html::{Div, Input, Label}; use leptos::logging::debug_log; use leptos::prelude::*; +use leptos::web_sys::{File, FormData}; +use leptos_oidc::AuthSignal; use leptos_use::{UseDropZoneOptions, UseDropZoneReturn, use_drop_zone_with_options}; +use crate::pages::api::files::upload_file; #[component] -pub fn FileDrop() -> impl IntoView { - // I'm really not sure how to hydrate this, but we'll try our best to do it somehow. - // We just need to know the file names and the size, not actually have the file itself. +pub fn FileDrop( + #[prop(into)] name: String, + #[prop(into)] form_id: String, +) -> impl IntoView { + let zone_ref = NodeRef::
} } \ No newline at end of file From b932388092c049c9878949bdb2a619a6a9cdabc9 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Sun, 1 Mar 2026 12:34:02 -0500 Subject: [PATCH 10/21] feat: style file input --- src/components/file_drop.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/file_drop.rs b/src/components/file_drop.rs index a509361..c084b40 100644 --- a/src/components/file_drop.rs +++ b/src/components/file_drop.rs @@ -1,7 +1,8 @@ -use leptos::html::{Div, Input, Label}; +use leptos::html::{Input, Label}; use leptos::logging::debug_log; use leptos::prelude::*; use leptos::web_sys::{File, FormData}; +use leptos_icons::Icon; use leptos_oidc::AuthSignal; use leptos_use::{UseDropZoneOptions, UseDropZoneReturn, use_drop_zone_with_options}; use crate::pages::api::files::upload_file; @@ -105,12 +106,18 @@ pub fn FileDrop(
diff --git a/src/pages/api/files.rs b/src/pages/api/files.rs index b36249f..3aa6d88 100644 --- a/src/pages/api/files.rs +++ b/src/pages/api/files.rs @@ -1,13 +1,13 @@ -use leptos::prelude::*; -use server_fn::codec::{MultipartFormData, MultipartData}; -use leptos::logging::debug_log; +use leptos::logging::debug_log; +use leptos::prelude::*; +use server_fn::codec::{MultipartData, MultipartFormData}; #[server(input = MultipartFormData)] -pub async fn upload_file( - data: MultipartData -) -> Result<(), ServerFnError> { +pub async fn upload_file(data: MultipartData) -> Result<(), ServerFnError> { + use super::{MAIN_TABLE_NAME, S3_BUCKET_NAME}; use crate::pages::api::tokens::validate_and_get_token_info; use crate::utils::server::create_aws_config; + use aws_sdk_dynamodb::types::AttributeValue; let mut multipart = data.into_inner().unwrap(); @@ -15,6 +15,7 @@ pub async fn upload_file( let mut file_bytes = Vec::::new(); let mut form_id = String::new(); let mut access_token = String::new(); + let mut input_name = String::new(); while let Ok(Some(field)) = multipart.next_field().await { let field_name = field.name().unwrap_or_default(); @@ -26,46 +27,104 @@ pub async fn upload_file( // NOTE: If there are multiple files put here the function will only upload the last // one in the data. // We should likely use chunk streaming in the future, for safety. - file_name = field.file_name() + input_name = field + .name() + .ok_or(ServerFnError::new("Couldn't find input name"))? + .to_owned(); + file_name = field + .file_name() .ok_or(ServerFnError::new("Couldn't find file name"))? .to_owned(); file_bytes = field.bytes().await?.to_vec(); - }, + } } } - if access_token.is_empty() { return Err(ServerFnError::new("Missing access token")); } - if form_id.is_empty() { return Err(ServerFnError::new("Missing form ID")); } - if file_name.is_empty() { return Err(ServerFnError::new("Missing file")); } + if access_token.is_empty() { + return Err(ServerFnError::new("Missing access token")); + } + if form_id.is_empty() { + return Err(ServerFnError::new("Missing form ID")); + } + if file_name.is_empty() { + return Err(ServerFnError::new("Missing file")); + } + if input_name.is_empty() { + return Err(ServerFnError::new("Missing input file")); + } let user_claims = validate_and_get_token_info(access_token).await?; let subject = user_claims.subject; - let key = format!("{form_id}/{subject}/{file_name}"); + let key = format!("{form_id}/{subject}/{input_name}/{file_name}"); debug_log!("Adding file to S3: {:?}", key); - let client = aws_sdk_s3::Client::new(&create_aws_config().await); + let s3_client = aws_sdk_s3::Client::new(&create_aws_config().await); - client + s3_client .put_object() - .bucket("leptos-scholarships") - .key(key) + .bucket(S3_BUCKET_NAME) + .key(&key) .body(file_bytes.into()) .send() .await .map(|_| ()) - .map_err(ServerFnError::from) + .map_err(|e| ServerFnError::new(format!("Couldn't put file to S3: {}", e.to_string())))?; + + // 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 + // uploaded, or a list of all files submitted for a specific question with their keys for + // additional access. + let dynamo_client = aws_sdk_dynamodb::Client::new(&create_aws_config().await); + + let dynamo_result = dynamo_client + .put_item() + .table_name(MAIN_TABLE_NAME) + .item( + "HK".to_string(), + AttributeValue::S(format!("STUDENT#{subject}")), + ) + .item( + "SK".to_string(), + AttributeValue::S(format!("FILE#{form_id}#{input_name}")), + ) + .item("file_name".to_string(), AttributeValue::S(file_name)) + .item("file_key".to_string(), AttributeValue::S(key.clone())) + .send() + .await + .map(|_| ()) + .map_err(|e| ServerFnError::new(format!("Couldn't put file to Dynamo: {}", e.to_string()))); + + if let Err(_) = dynamo_result { + // Roll back file insertion to S3. Only attempted on Dynamo insertion failure. + s3_client + .delete_object() + .bucket("leptos-scholarships") + .key(&key) + .send() + .await + .map(|_| ()) + .map_err(|e| { + ServerFnError::new(format!( + "Couldn't roll back S3 operation: {}", + e.to_string() + )) + }) + } else { + dynamo_result + } } #[server] pub async fn delete_file( access_token: String, form_id: String, - file_name: String + file_name: String, ) -> Result<(), ServerFnError> { - use crate::utils::server::create_aws_config; + use super::S3_BUCKET_NAME; use crate::pages::api::tokens::validate_and_get_token_info; + use crate::utils::server::create_aws_config; let user_claims = validate_and_get_token_info(access_token).await?; let subject = user_claims.subject; @@ -76,7 +135,7 @@ pub async fn delete_file( client .delete_object() - .bucket("leptos-scholarhips") + .bucket(S3_BUCKET_NAME) .key(key) .send() .await diff --git a/src/pages/student/additional_page.rs b/src/pages/student/additional_page.rs index d9cc319..ee6bdea 100644 --- a/src/pages/student/additional_page.rs +++ b/src/pages/student/additional_page.rs @@ -1,5 +1,5 @@ -use leptos::prelude::*; use crate::components::FileDrop; +use leptos::prelude::*; #[component] pub fn AdditionalPage() -> impl IntoView { @@ -10,7 +10,7 @@ pub fn AdditionalPage() -> impl IntoView { "This is just a placeholder for now. We'll show redirect buttons to each form."
"Here's a testing file input!"
- +
} -} \ No newline at end of file +} From 6f16bbe3117d0ae0b010375ebfd063316d967e5b Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Mon, 2 Mar 2026 12:29:49 -0500 Subject: [PATCH 14/21] feat: allow reactive updates of UI with inserted file names --- src/components/file_drop.rs | 33 +++++++++++++-------------------- src/pages/api/files.rs | 28 ++++++++++++++++++---------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/components/file_drop.rs b/src/components/file_drop.rs index ac09fdc..66f72d7 100644 --- a/src/components/file_drop.rs +++ b/src/components/file_drop.rs @@ -18,12 +18,12 @@ pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> im let form_id = StoredValue::new(form_id); let auth = expect_context::(); - let upload_action = StoredValue::new(Action::new(|form_data: &FormData| { + let upload_action = Action::new(|form_data: &FormData| { let form_data = form_data.clone(); async move { upload_file(form_data.into()).await } - })); + }); - let uploading = upload_action.get_value().pending(); + let uploading = upload_action.pending(); let upload_files = Callback::new(move |files: Vec| { let form_data = FormData::new().unwrap(); @@ -52,7 +52,7 @@ pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> im } // Upload file to server. - upload_action.get_value().dispatch(form_data); + upload_action.dispatch(form_data); }); let UseDropZoneReturn { @@ -64,11 +64,6 @@ pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> im .on_drop(move |ev| { let file = ev.files.first().unwrap(); - // Update file list - file_name_list.update(|list| { - list.push(file.name()); - }); - upload_files.run(vec![file.to_owned()]); }), ); @@ -88,20 +83,18 @@ pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> im }) .unwrap_or_default(); - file_name_list.update({ - let files = files.clone(); - |list| { - for file in files { - if !list.iter().any(|f| *f == file.name()) { - list.push(file.name()); - } - } - } - }); - upload_files.run(files); }; + // Add file names to the list when upload_action is successful. + Effect::new(move || { + if let Some(Ok(uploaded_file_name)) = upload_action.value().get() { + file_name_list.update(|list| { + list.push(uploaded_file_name); + }); + } + }); + // let on_click_file_delete = move |file_name, form_id| { // // }; diff --git a/src/pages/api/files.rs b/src/pages/api/files.rs index 3aa6d88..a46b9ae 100644 --- a/src/pages/api/files.rs +++ b/src/pages/api/files.rs @@ -3,7 +3,7 @@ use leptos::prelude::*; use server_fn::codec::{MultipartData, MultipartFormData}; #[server(input = MultipartFormData)] -pub async fn upload_file(data: MultipartData) -> Result<(), ServerFnError> { +pub async fn upload_file(data: MultipartData) -> Result { use super::{MAIN_TABLE_NAME, S3_BUCKET_NAME}; use crate::pages::api::tokens::validate_and_get_token_info; use crate::utils::server::create_aws_config; @@ -89,31 +89,39 @@ pub async fn upload_file(data: MultipartData) -> Result<(), ServerFnError> { "SK".to_string(), AttributeValue::S(format!("FILE#{form_id}#{input_name}")), ) - .item("file_name".to_string(), AttributeValue::S(file_name)) + .item( + "file_name".to_string(), + AttributeValue::S(file_name.clone()), + ) .item("file_key".to_string(), AttributeValue::S(key.clone())) .send() .await - .map(|_| ()) - .map_err(|e| ServerFnError::new(format!("Couldn't put file to Dynamo: {}", e.to_string()))); + .map_err(|e| { + ServerFnError::new(format!( + "Couldn't put file to Dynamo, file rolled back: {}", + e.to_string() + )) + }); - if let Err(_) = dynamo_result { - // Roll back file insertion to S3. Only attempted on Dynamo insertion failure. + // If Dynamo fails, we want to handle the error by rolling back the file. Then, we return a failure. + if let Err(err) = dynamo_result { s3_client .delete_object() .bucket("leptos-scholarships") .key(&key) .send() .await - .map(|_| ()) .map_err(|e| { ServerFnError::new(format!( "Couldn't roll back S3 operation: {}", e.to_string() )) - }) - } else { - dynamo_result + })?; + + return Err(err); } + + Ok(file_name) } #[server] From c0351151260318a9f2f1fab795ea451ea5318508 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Mon, 2 Mar 2026 13:40:00 -0500 Subject: [PATCH 15/21] feat: allow deletion of files from s3 --- src/components/file_drop.rs | 55 +++++++++++++++++++++++++++++-------- src/pages/api/files.rs | 53 +++++++++++++++++++++++++++++------ 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/src/components/file_drop.rs b/src/components/file_drop.rs index 66f72d7..3774c99 100644 --- a/src/components/file_drop.rs +++ b/src/components/file_drop.rs @@ -1,4 +1,4 @@ -use crate::pages::api::files::upload_file; +use crate::pages::api::files::{DeleteFile, upload_file}; use leptos::html::{Input, Label}; use leptos::logging::debug_log; use leptos::prelude::*; @@ -23,7 +23,10 @@ pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> im async move { upload_file(form_data.into()).await } }); + let delete_action = ServerAction::::new(); + let uploading = upload_action.pending(); + let deleting = delete_action.pending(); let upload_files = Callback::new(move |files: Vec| { let form_data = FormData::new().unwrap(); @@ -86,6 +89,19 @@ pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> im upload_files.run(files); }; + let on_click_file_delete = move |file_name: String| { + let access_token = auth + .try_with(|a| a.authenticated().map(|a| a.access_token())) + .flatten(); + + delete_action.dispatch(DeleteFile { + form_id: form_id.get_value(), + input_name: name.get_value(), + access_token: access_token.unwrap_or_default(), + file_name, + }) + }; + // Add file names to the list when upload_action is successful. Effect::new(move || { if let Some(Ok(uploaded_file_name)) = upload_action.value().get() { @@ -95,9 +111,14 @@ pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> im } }); - // let on_click_file_delete = move |file_name, form_id| { - // - // }; + // Remove file name from the list when delete_action is successful. + Effect::new(move || { + if let Some(Ok(deleted_file_name)) = delete_action.value().get() { + file_name_list.update(|list| { + list.retain(|f| *f != deleted_file_name); + }); + } + }); view! { - -
-
{file}
-
- -
-
-
+ +
{file.get_value()}
+
+ +
+
+ } + } + /> } } diff --git a/src/pages/api/files.rs b/src/pages/api/files.rs index a46b9ae..22f3bb9 100644 --- a/src/pages/api/files.rs +++ b/src/pages/api/files.rs @@ -87,7 +87,7 @@ pub async fn upload_file(data: MultipartData) -> Result { ) .item( "SK".to_string(), - AttributeValue::S(format!("FILE#{form_id}#{input_name}")), + AttributeValue::S(format!("FILE#{form_id}#{input_name}#{file_name}")), ) .item( "file_name".to_string(), @@ -128,25 +128,62 @@ pub async fn upload_file(data: MultipartData) -> Result { pub async fn delete_file( access_token: String, form_id: String, + input_name: String, file_name: String, -) -> Result<(), ServerFnError> { - use super::S3_BUCKET_NAME; +) -> Result { + use super::{MAIN_TABLE_NAME, S3_BUCKET_NAME}; use crate::pages::api::tokens::validate_and_get_token_info; use crate::utils::server::create_aws_config; + use aws_sdk_dynamodb::types::{AttributeValue, ReturnValue}; let user_claims = validate_and_get_token_info(access_token).await?; let subject = user_claims.subject; - let key = format!("{form_id}/{subject}/{file_name}"); + let entry_hk = format!("STUDENT#{subject}"); + let entry_sk = format!("FILE#{form_id}#{input_name}#{file_name}"); - let client = aws_sdk_s3::Client::new(&create_aws_config().await); + let key = format!("{form_id}/{subject}/{input_name}/{file_name}"); - client + let config = create_aws_config().await; + let s3_client = aws_sdk_s3::Client::new(&config); + let dynamo_client = aws_sdk_dynamodb::Client::new(&config); + + let previous_values = dynamo_client + .delete_item() + .table_name(MAIN_TABLE_NAME) + .key("HK".to_string(), AttributeValue::S(entry_hk)) + .key("SK".to_string(), AttributeValue::S(entry_sk)) + .return_values(ReturnValue::AllOld) + .send() + .await? + .attributes + .unwrap_or_default(); + + let s3_result = s3_client .delete_object() .bucket(S3_BUCKET_NAME) .key(key) .send() .await - .map(|_| ()) - .map_err(ServerFnError::from) + .map_err(ServerFnError::from); + + if let Err(err) = s3_result { + // Add the key back to Dynamo and return an error. + dynamo_client + .put_item() + .table_name(MAIN_TABLE_NAME) + .set_item(Some(previous_values)) + .send() + .await + .map_err(|err| { + ServerFnError::new(format!( + "Couldn't roll back Dynamo operation: {}", + err.to_string() + )) + })?; + + return Err(err); + } + + Ok(file_name) } From e8b0ceeb7bb762fe84577d64c3f8f718d1a93cb5 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Mon, 2 Mar 2026 13:48:29 -0500 Subject: [PATCH 16/21] feat: create financial_page --- src/app.rs | 1 + src/pages/student/additional_page.rs | 3 --- src/pages/student/financial_page.rs | 22 ++++++++++++++++++++++ src/pages/student/mod.rs | 2 ++ src/pages/student/shell.rs | 1 + 5 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 src/pages/student/financial_page.rs diff --git a/src/app.rs b/src/app.rs index 75517b3..ad456c6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -148,6 +148,7 @@ fn StudentRoutes() -> impl MatchNestedRoutes + Clone { /> + } .into_inner() diff --git a/src/pages/student/additional_page.rs b/src/pages/student/additional_page.rs index ee6bdea..5796d1a 100644 --- a/src/pages/student/additional_page.rs +++ b/src/pages/student/additional_page.rs @@ -1,4 +1,3 @@ -use crate::components::FileDrop; use leptos::prelude::*; #[component] @@ -9,8 +8,6 @@ pub fn AdditionalPage() -> impl IntoView {
"This is just a placeholder for now. We'll show redirect buttons to each form."
-
"Here's a testing file input!"
-
} } diff --git a/src/pages/student/financial_page.rs b/src/pages/student/financial_page.rs new file mode 100644 index 0000000..8cab91b --- /dev/null +++ b/src/pages/student/financial_page.rs @@ -0,0 +1,22 @@ +use crate::components::{FileDrop, Header}; +use leptos::prelude::*; + +#[component] +pub fn StudentFinancialPage() -> impl IntoView { + // This is where we'll get information about the files that have been uploaded. + // For now, we'll leave it blank. + + view! { +
+
+ // +
+ + // +
+
+ } +} diff --git a/src/pages/student/mod.rs b/src/pages/student/mod.rs index cfe4aaa..863d74b 100644 --- a/src/pages/student/mod.rs +++ b/src/pages/student/mod.rs @@ -5,6 +5,7 @@ mod demographics_page; mod eligibility_page; mod extracurriculars_page; mod family_info_page; +mod financial_page; mod form_setup; mod home_page; mod shell; @@ -19,6 +20,7 @@ pub use demographics_page::*; pub use eligibility_page::*; pub use extracurriculars_page::*; pub use family_info_page::*; +pub use financial_page::*; pub use home_page::*; pub use shell::*; pub use test_display::*; diff --git a/src/pages/student/shell.rs b/src/pages/student/shell.rs index 023f24e..8788048 100644 --- a/src/pages/student/shell.rs +++ b/src/pages/student/shell.rs @@ -35,6 +35,7 @@ pub fn StudentShell() -> impl IntoView { SubTabInfo::new("Extracurriculars", "extracurriculars"), SubTabInfo::new("University Info", "university"), SubTabInfo::new("Family Info", "family-info"), + SubTabInfo::new("Financials", "financials"), ], ), ), From cc09769f2889dad660659b60808f0a1e3cfabe35 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Mon, 2 Mar 2026 13:48:38 -0500 Subject: [PATCH 17/21] chore: style file list --- src/components/file_drop.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/file_drop.rs b/src/components/file_drop.rs index 3774c99..ef61e9f 100644 --- a/src/components/file_drop.rs +++ b/src/components/file_drop.rs @@ -147,7 +147,7 @@ pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> im on_click_file_delete(file.get_value()); }; view! { -
+
{file.get_value()}
From 9d156c17e976c3017cf59aac6a9a20bdc8e6aa53 Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Mon, 2 Mar 2026 14:25:07 -0500 Subject: [PATCH 18/21] feat: implement file list retrieval for file drop components --- src/components/file_drop.rs | 16 +++++++- src/pages/api/files.rs | 61 +++++++++++++++++++++++++---- src/pages/student/financial_page.rs | 25 +++++++++++- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/src/components/file_drop.rs b/src/components/file_drop.rs index ef61e9f..8580442 100644 --- a/src/components/file_drop.rs +++ b/src/components/file_drop.rs @@ -8,7 +8,11 @@ use leptos_oidc::AuthSignal; use leptos_use::{UseDropZoneOptions, UseDropZoneReturn, use_drop_zone_with_options}; #[component] -pub fn FileDrop(#[prop(into)] name: String, #[prop(into)] form_id: String) -> impl IntoView { +pub fn FileDrop( + #[prop(into)] name: String, + #[prop(into)] form_id: String, + #[prop()] existing_files: Resource, ServerFnError>>, +) -> impl IntoView { let zone_ref = NodeRef::