feat: implement applicant page shell

This commit is contained in:
alphafetus committed 2026-03-18 13:48:26 -04:00
1 parent 2e134fb693
commit 05b865c608
2 files changed
+83 -31

No files matched your search

+4 -2
View File
@@ -1,11 +1,11 @@
use crate::components::ToastList; use crate::components::ToastList;
use crate::components::login::ProviderLoginContext; use crate::components::login::ProviderLoginContext;
use crate::pages::student;
use crate::pages::{ use crate::pages::{
AdminHomePage, AdminProviderPage, AdminScholarshipPage, AdminShell, AdminUtilsPage, AdminHomePage, AdminProviderPage, AdminScholarshipPage, AdminShell, AdminUtilsPage,
ApplicantsPageFallback, AuthCallbackPage, LoanerBorrowForm, LoanerFallback, LoanerReturnForm, ApplicantsPageFallback, AuthCallbackPage, LoanerBorrowForm, LoanerFallback, LoanerReturnForm,
LoanerShell, ProviderContactPage, ProviderPortal, ScholarshipInfoPage, LoanerShell, ProviderContactPage, ProviderPortal, ScholarshipInfoPage,
}; };
use crate::pages::{ApplicantsPageShell, student};
use leptos::prelude::*; use leptos::prelude::*;
use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context}; use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context};
use leptos_oidc::AuthSignal; use leptos_oidc::AuthSignal;
@@ -86,7 +86,9 @@ fn ProviderRoutes() -> impl MatchNestedRoutes + Clone {
view! { view! {
<ParentRoute path=path!("/providers") view=ProviderLoginContext> <ParentRoute path=path!("/providers") view=ProviderLoginContext>
<Route path=path!("") view=ProviderPortal /> <Route path=path!("") view=ProviderPortal />
<Route path=path!("/applicants") view=ApplicantsPageFallback /> <ParentRoute path=path!("/applicants") view=ApplicantsPageShell>
<Route path=path!("") view=ApplicantsPageFallback />
</ParentRoute>
<Route path=path!("/callback") view=AuthCallbackPage /> <Route path=path!("/callback") view=AuthCallbackPage />
<Route path=path!("/profile") view=ProviderContactPage /> <Route path=path!("/profile") view=ProviderContactPage />
// <Route path=path!("/scholarships") view=FormClosedPage /> // <Route path=path!("/scholarships") view=FormClosedPage />
+79 -29
View File
@@ -1,20 +1,18 @@
use crate::common::ValueType; use crate::common::{ExpandableInfo, ValueType};
use crate::components::{Banner, Loading}; use crate::components::{Banner, Loading};
use leptos::either::Either;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_oidc::AuthSignal; use leptos_oidc::AuthSignal;
use leptos_router::components::Outlet;
use leptos_router::hooks::use_navigate;
#[component] #[component]
pub fn ApplicantsPageFallback() -> impl IntoView { pub fn ApplicantsPageFallback() -> impl IntoView {
view! { view! {
<Banner title="R15 Scholarships" logo="/PHS_Stacked_Acronym.png" path="/providers" /> <div class="flex flex-col flex-1 gap-2">
<div class="flex flex-col gap-4 mt-3 items-center justify-center"> <h1 class="flex-1 text-center font-bold text-2xl">"Scholarship Applicants"</h1>
<h1 class="text-3xl font-bold">"Applicants Page"</h1> <p class="text-center text-lg">
"Please select a scholarship from the left to view the eligible applicants."
<p>
"This page is under construction! It will show all the students that are eligible for your scholarship(s)."
</p> </p>
<p>"Come back soon!"</p>
</div> </div>
} }
} }
@@ -29,7 +27,19 @@ pub fn ApplicantsPageShell() -> impl IntoView {
// The content will be shown within the right side, but only as a result of some subroutes, // The content will be shown within the right side, but only as a result of some subroutes,
// each of which is still considered a single page. // each of which is still considered a single page.
view! { <div></div> } view! {
<Banner title="R15 Scholarships" logo="/PHS_Stacked_Acronym.png" path="/providers" />
<div class="flex flex-row mt-5">
<div class="flex-1" />
<div class="flex-3 flex flex-row">
<ApplicantsScholarshipList />
<div class="flex-3 text-center">
<Outlet />
</div>
</div>
<div class="flex-1" />
</div>
}
} }
#[component] #[component]
@@ -82,8 +92,17 @@ fn ApplicantsScholarshipList() -> impl IntoView {
}, },
); );
let navigate = use_navigate();
// This closure returns another closure that's using the correct scholarship ID.
// It avoids having to define it within the Transition component. It's wrapped in a Callback
// because this makes it Send + Sync.
let on_view_click = Callback::new(move |scholarship_id: String| {
navigate(&*scholarship_id, Default::default());
});
view! { view! {
<div class="flex flex-col rounded-md shadow-lg/33 p-2"> <div class="flex flex-col flex-1 rounded-md shadow-lg/33 p-2 gap-3">
<h2 class="text-xl font-bold flex-1 text-center">"Scholarships"</h2> <h2 class="text-xl font-bold flex-1 text-center">"Scholarships"</h2>
<Transition fallback=Loading> <Transition fallback=Loading>
{move || { {move || {
@@ -93,26 +112,28 @@ fn ApplicantsScholarshipList() -> impl IntoView {
let items = match items_res { let items = match items_res {
Ok(items) => items, Ok(items) => items,
Err(e) => { Err(e) => {
return Either::Left( return view! {
view! { <div>
<div> {format!("Couldn't get scholarships: {}", e.to_string())}
{format!("Couldn't get scholarships: {}", e.to_string())} </div>
</div> }
} .into_any();
.into_any(),
);
} }
}; };
Either::Right(
items
.into_iter()
.map(|item| {
view! { <ApplicantsScholarshipEntry /> } view! {
.into_any() <For
}) each=move || items.clone()
.collect_view(), key=|item| item.subject.clone()
) let(item)
>
<ApplicantsScholarshipEntry
item=item
on_view_click=on_view_click
/>
</For>
}
.into_any()
}) })
.collect_view() .collect_view()
}} }}
@@ -122,7 +143,36 @@ fn ApplicantsScholarshipList() -> impl IntoView {
} }
#[component] #[component]
fn ApplicantsScholarshipEntry() -> impl IntoView {} fn ApplicantsScholarshipEntry(
#[prop()] item: ExpandableInfo,
#[prop(into)] on_view_click: Callback<String, ()>,
) -> impl IntoView {
// Store the item's subject.
let subject = StoredValue::new(item.subject);
let scholarship_name = Memo::new(move |_| {
item.data
.get("name")
.map(|name| name.as_string().ok().flatten())
.flatten()
.unwrap_or("<unnamed scholarship>".to_string())
});
let on_click = move |_| {
on_view_click.run(subject.get_value());
};
view! {
<div class="flex flex-col rounded-md shadow-md hover:shadow-lg/33 transition-shadow">
<div class="text-center p-2">{scholarship_name}</div>
<div
class="flex-1 font-bold text-white rounded-b-md text-center cursor-pointer bg-red-800 hover:bg-red-900 p-2 transition-bg"
on:click=on_click
>
"View Applicants"
</div>
</div>
}
}
#[component] #[component]
fn ApplicantsStudentList() -> impl IntoView {} fn ApplicantsStudentList() -> impl IntoView {}