Implement provider scholarship edit form #33
No files matched your search
+11
-4
@@ -3,7 +3,8 @@ use leptos::prelude::*;
|
|||||||
use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context};
|
use leptos_meta::{MetaTags, Stylesheet, Title, provide_meta_context};
|
||||||
use leptos_oidc::{Auth, AuthParameters, AuthSignal, Challenge};
|
use leptos_oidc::{Auth, AuthParameters, AuthSignal, Challenge};
|
||||||
use leptos_router::{components::{Route, Router, Routes}, path};
|
use leptos_router::{components::{Route, Router, Routes}, path};
|
||||||
use crate::pages::{AboutPage, ComparisonTestPage, HomePage, LoanerPage, TestPage};
|
use leptos_router::components::ParentRoute;
|
||||||
|
use crate::pages::{AboutPage, ComparisonTestPage, HomePage, LoanerPage, ScholarshipInfoPage, TestPage};
|
||||||
|
|
||||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
@@ -96,7 +97,7 @@ pub fn AppWithRoutes() -> impl IntoView {
|
|||||||
|
|
||||||
// sets the document title
|
// sets the document title
|
||||||
<Title text="R15 Scholarship App DEV" />
|
<Title text="R15 Scholarship App DEV" />
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
// TODO Create a 404 page
|
// TODO Create a 404 page
|
||||||
<Routes fallback=|| "Page not found.".into_view()>
|
<Routes fallback=|| "Page not found.".into_view()>
|
||||||
@@ -104,8 +105,14 @@ pub fn AppWithRoutes() -> impl IntoView {
|
|||||||
<Route path=path!("about") view=AboutPage/>
|
<Route path=path!("about") view=AboutPage/>
|
||||||
<Route path=path!("test_page") view=TestPage/>
|
<Route path=path!("test_page") view=TestPage/>
|
||||||
<Route path=path!("comparison") view=ComparisonTestPage />
|
<Route path=path!("comparison") view=ComparisonTestPage />
|
||||||
<Route path=path!("loaners") view=LoanerPage />
|
<ParentRoute path=path!("providers/scholarships") view=ScholarshipInfoPage>
|
||||||
<Route path=path!("loaners/:form_name") view=LoanerPage />
|
<Route path=path!(":id") view=ScholarshipInfoPage />
|
||||||
|
<Route path=path!("") view=ScholarshipInfoPage />
|
||||||
|
</ParentRoute>
|
||||||
|
<ParentRoute path=path!("loaners") view=LoanerPage>
|
||||||
|
<Route path=path!("") view=LoanerPage />
|
||||||
|
<Route path=path!(":form_name") view=LoanerPage />
|
||||||
|
</ParentRoute>
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use leptos::prelude::*;
|
|||||||
pub fn Loading() -> impl IntoView {
|
pub fn Loading() -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
<div>
|
<div>
|
||||||
<img src="loading.gif" />
|
<img src="/loading.gif" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,8 @@ use aws_sdk_dynamodb::{
|
|||||||
use leptos::leptos_dom::log;
|
use leptos::leptos_dom::log;
|
||||||
use leptos::prelude::ServerFnError;
|
use leptos::prelude::ServerFnError;
|
||||||
use leptos::server;
|
use leptos::server;
|
||||||
use crate::common::ExpandableInfo;
|
use uuid::Uuid;
|
||||||
|
use crate::common::{ExpandableInfo, ValueType};
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use crate::pages::utils::server_utils::create_dynamo_client;
|
use crate::pages::utils::server_utils::create_dynamo_client;
|
||||||
@@ -89,3 +90,77 @@ pub async fn get_all_scholarship_info() -> Result<Vec<ExpandableInfo>, ServerFnE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets all scholarships that are associated with the given scholarship provider's ID.
|
||||||
|
#[server(GetProviderScholarships, "/providers/scholarships/get")]
|
||||||
|
pub async fn get_provider_scholarships(provider_id: String) -> Result<Vec<ExpandableInfo>, ServerFnError> {
|
||||||
|
let client = create_dynamo_client().await;
|
||||||
|
|
||||||
|
log!("Getting provider scholarships for provider with ID {:?}", provider_id);
|
||||||
|
|
||||||
|
match client
|
||||||
|
.scan()
|
||||||
|
.table_name("leptos-scholarship-test")
|
||||||
|
.expression_attribute_values(":id", serde_dynamo::to_attribute_value(ValueType::String(Some(provider_id)))?)
|
||||||
|
.filter_expression("provider_id = :id")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(output) => {
|
||||||
|
if let Some(items) = output.items {
|
||||||
|
Ok(serde_dynamo::from_items(items)?)
|
||||||
|
} else {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let msg = err.message().unwrap_or("An unknown error occurred");
|
||||||
|
Err(ServerFnError::new(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new scholarship with a unique ID, and then returns that ID.
|
||||||
|
#[server(RegisterScholarship, "/providers/scholarships/create")]
|
||||||
|
pub async fn register_scholarship(provider_id: String, scholarship_name: String) -> Result<String, ServerFnError> {
|
||||||
|
let client = create_dynamo_client().await;
|
||||||
|
|
||||||
|
log!("Creating scholarship for provider with ID {:?}", provider_id);
|
||||||
|
|
||||||
|
let mut current_uuid = "testing".to_string();
|
||||||
|
let mut item = ExpandableInfo::new(current_uuid.clone());
|
||||||
|
item.data.insert("provider_id".to_string(), ValueType::String(Some(provider_id)));
|
||||||
|
item.data.insert("scholarship_name".to_string(), ValueType::String(Some(scholarship_name)));
|
||||||
|
loop {
|
||||||
|
let ser_item = serde_dynamo::to_item(&item)?;
|
||||||
|
match client
|
||||||
|
.put_item()
|
||||||
|
.table_name("leptos-scholarship-test")
|
||||||
|
.set_item(Some(ser_item))
|
||||||
|
.condition_expression("attribute_not_exists(subject)")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
// Return the uuid that we used.
|
||||||
|
return Ok(current_uuid);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// Check the error to figure out if it was a key validation error.
|
||||||
|
// If so, retry with new values.
|
||||||
|
match err.code() {
|
||||||
|
Some("ConditionalCheckFailedException") => {
|
||||||
|
log!("Retrying conditional check.");
|
||||||
|
log!("Current error: {:?}", err);
|
||||||
|
current_uuid = Uuid::new_v4().to_string();
|
||||||
|
item.subject = current_uuid.clone();
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ServerFnError::new(err.message().unwrap_or("An unknown error occurred")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,26 +2,20 @@ use leptos::either::Either;
|
|||||||
use super::home_page::get_submission;
|
use super::home_page::get_submission;
|
||||||
use super::{UnauthenticatedPage};
|
use super::{UnauthenticatedPage};
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
ComparisonData, ExpandableInfo, UserClaims, ValueType,
|
ComparisonData, ExpandableInfo, ValueType,
|
||||||
};
|
};
|
||||||
use crate::components::{ActionButton, Loading, Panel, Row};
|
use crate::components::{ActionButton, Loading, Panel, Row};
|
||||||
use leptos::leptos_dom::logging::console_log;
|
use leptos::leptos_dom::logging::console_log;
|
||||||
use leptos::logging::log;
|
use leptos::logging::log;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
|
use leptos_oidc::{AuthLoaded, Authenticated};
|
||||||
|
use crate::pages::utils::get_user_claims;
|
||||||
use super::api::{get_comparison_info, get_all_scholarship_info};
|
use super::api::{get_comparison_info, get_all_scholarship_info};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ComparisonTestPage() -> impl IntoView {
|
pub fn ComparisonTestPage() -> impl IntoView {
|
||||||
let result_msg = RwSignal::new("Found submission, waiting for input.".to_string());
|
let result_msg = RwSignal::new("Found submission, waiting for input.".to_string());
|
||||||
let auth = use_context::<AuthSignal>().expect("Couldn't find user information.");
|
let user_claims = get_user_claims();
|
||||||
let user_claims = Signal::derive(move || {
|
|
||||||
auth.with(|auth| {
|
|
||||||
auth.authenticated().and_then(|data| {
|
|
||||||
data.decoded_access_token::<UserClaims>(Algorithm::RS256, &["account"])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note that the value passed in MUST be equatable.
|
// Note that the value passed in MUST be equatable.
|
||||||
// We get/unwrap the value repeatedly until we get a simple string value, then clone it so that
|
// We get/unwrap the value repeatedly until we get a simple string value, then clone it so that
|
||||||
|
|||||||
+4
-11
@@ -9,7 +9,7 @@ use std::process::{Command, Stdio};
|
|||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use crate::common::{ExpandableInfo, UserClaims};
|
use crate::common::ExpandableInfo;
|
||||||
use crate::pages::UnauthenticatedPage;
|
use crate::pages::UnauthenticatedPage;
|
||||||
use crate::components::{
|
use crate::components::{
|
||||||
ActionButton, CheckboxList, Loading, MultiEntry, OutlinedTextField, Panel, RadioList, Row,
|
ActionButton, CheckboxList, Loading, MultiEntry, OutlinedTextField, Panel, RadioList, Row,
|
||||||
@@ -18,12 +18,13 @@ use crate::components::{
|
|||||||
use crate::input;
|
use crate::input;
|
||||||
use leptos::leptos_dom::logging::console_log;
|
use leptos::leptos_dom::logging::console_log;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
|
use leptos_oidc::{AuthLoaded, Authenticated};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use leptos::task::spawn_local;
|
use leptos::task::spawn_local;
|
||||||
use leptos::web_sys::HtmlAnchorElement;
|
use leptos::web_sys::HtmlAnchorElement;
|
||||||
use leptos::wasm_bindgen::JsCast;
|
use leptos::wasm_bindgen::JsCast;
|
||||||
use traits::{AsReactive, ReactiveCapture};
|
use traits::{AsReactive, ReactiveCapture};
|
||||||
|
use crate::pages::utils::get_user_claims;
|
||||||
|
|
||||||
/// # Get Student Info
|
/// # Get Student Info
|
||||||
/// Gets a student's information given their `subject`.
|
/// Gets a student's information given their `subject`.
|
||||||
@@ -146,15 +147,7 @@ pub async fn create_example_pdf() -> Result<Vec<u8>, ServerFnError> {
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn HomePage() -> impl IntoView {
|
pub fn HomePage() -> impl IntoView {
|
||||||
// Creates a reactive value to update the button
|
// Creates a reactive value to update the button
|
||||||
|
let user_claims = get_user_claims();
|
||||||
let auth = use_context::<AuthSignal>().expect("Couldn't find user information.");
|
|
||||||
let user_claims = Signal::derive(move || {
|
|
||||||
auth.with(|auth| {
|
|
||||||
auth.authenticated().and_then(|data| {
|
|
||||||
data.decoded_access_token::<UserClaims>(Algorithm::RS256, &["account"])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Note that the value passed in MUST be equatable.
|
// Note that the value passed in MUST be equatable.
|
||||||
// We get/unwrap the value repeatedly until we get a simple string value, then clone it so that
|
// We get/unwrap the value repeatedly until we get a simple string value, then clone it so that
|
||||||
|
|||||||
+1
-1
@@ -11,5 +11,5 @@ mod loaner_page;
|
|||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
about_page::*, home_page::*, provider_portal::*, test_page::*, unauthenticated_page::*,
|
about_page::*, home_page::*, provider_portal::*, test_page::*, unauthenticated_page::*,
|
||||||
loaner_page::LoanerPage, comparison_test::*,
|
loaner_page::*, comparison_test::*, scholarship_info::*,
|
||||||
};
|
};
|
||||||
+154
-10
@@ -1,13 +1,15 @@
|
|||||||
|
use leptos::either::Either;
|
||||||
|
|
|||||||
use leptos::logging::log;
|
use leptos::logging::log;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_oidc::{AuthLoaded, Authenticated};
|
use leptos_oidc::{AuthLoaded, Authenticated};
|
||||||
use leptos_router::hooks::use_params;
|
use leptos_router::hooks::{use_navigate, use_params};
|
||||||
use crate::common::{ExpandableInfo, ScholarshipFormParams};
|
use crate::common::{ExpandableInfo, ScholarshipFormParams, ValueType};
|
||||||
use crate::components::{ActionButton, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row};
|
use crate::components::{ActionButton, Banner, ChipsList, Loading, OutlinedTextField, Panel, RadioList, Row};
|
||||||
use super::UnauthenticatedPage;
|
use super::UnauthenticatedPage;
|
||||||
use traits::{AsReactive, ReactiveCapture};
|
use traits::{AsReactive, ReactiveCapture};
|
||||||
use crate::common::ComparisonData;
|
use crate::common::ComparisonData;
|
||||||
use super::api::{get_comparison_info, get_scholarship_info, CreateScholarshipInfo, CreateTestComparisons};
|
use crate::pages::utils::get_user_claims;
|
||||||
|
use super::api::{get_comparison_info, get_provider_scholarships, get_scholarship_info, CreateScholarshipInfo, CreateTestComparisons, RegisterScholarship};
|
||||||
|
|
||||||
|
|
||||||
/// # Scholarship Info Page
|
/// # Scholarship Info Page
|
||||||
@@ -15,12 +17,8 @@ use super::api::{get_comparison_info, get_scholarship_info, CreateScholarshipInf
|
|||||||
/// scholarship ID and scholarship provider subject number. The form itself will only
|
/// scholarship ID and scholarship provider subject number. The form itself will only
|
||||||
/// track a single scholarship (for now) and so selection of what scholarship will be edited
|
/// track a single scholarship (for now) and so selection of what scholarship will be edited
|
||||||
/// must be handled *before* navigation to this page.
|
/// must be handled *before* navigation to this page.
|
||||||
///
|
|
||||||
/// In its current testing form, we'll be using a single fixed ID. This absolutely *MUST* be
|
|
||||||
/// changed before a full release.
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ScholarshipInfoPage() -> impl IntoView {
|
pub fn ScholarshipInfoPage() -> impl IntoView {
|
||||||
// let scholarship_id = String::from("test-scholarship-id2");
|
|
||||||
let url_params = use_params::<ScholarshipFormParams>();
|
let url_params = use_params::<ScholarshipFormParams>();
|
||||||
let scholarship_id = move || {
|
let scholarship_id = move || {
|
||||||
url_params.read()
|
url_params.read()
|
||||||
@@ -61,11 +59,17 @@ pub fn ScholarshipInfoPage() -> impl IntoView {
|
|||||||
// Remember that we only want to show the page after we've got authentication information
|
// Remember that we only want to show the page after we've got authentication information
|
||||||
// and if there's data for us to use.
|
// and if there's data for us to use.
|
||||||
view! {
|
view! {
|
||||||
|
<Banner
|
||||||
|
title="R15 Scholarships"
|
||||||
|
logo="/PHS_Stacked_Acronym.png"
|
||||||
|
path="/"
|
||||||
|
/>
|
||||||
<AuthLoaded fallback=Loading>
|
<AuthLoaded fallback=Loading>
|
||||||
<Authenticated unauthenticated=UnauthenticatedPage>
|
<Authenticated unauthenticated=UnauthenticatedPage>
|
||||||
<Suspense fallback=Loading>
|
<Suspense fallback=Loading>
|
||||||
<Row>
|
<div class="flex flex-row align-top items-start">
|
||||||
<div class="flex flex-col flex-1" />
|
<div class="flex flex-col flex-1" />
|
||||||
|
<ScholarshipList />
|
||||||
{move || Suspend::new(async move {
|
{move || Suspend::new(async move {
|
||||||
let response = get_scholarship_action.get();
|
let response = get_scholarship_action.get();
|
||||||
let comparisons = get_comparison_info.get();
|
let comparisons = get_comparison_info.get();
|
||||||
@@ -91,13 +95,153 @@ pub fn ScholarshipInfoPage() -> impl IntoView {
|
|||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col flex-1" />
|
<div class="flex flex-col flex-1" />
|
||||||
</Row>
|
</div>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Authenticated>
|
</Authenticated>
|
||||||
</AuthLoaded>
|
</AuthLoaded>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn ScholarshipList() -> impl IntoView {
|
||||||
|
// Get scholarship provider information from authentication. This element will not display
|
||||||
|
// unless that information is available.
|
||||||
|
let user_claims = get_user_claims();
|
||||||
|
let provider_id = Memo::new(move |_|
|
||||||
|
user_claims.get()
|
||||||
|
.as_ref()
|
||||||
|
.map(|info| info.claims.subject.clone())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register scholarship server resource
|
||||||
|
let scholarships = Resource::new(
|
||||||
|
move || provider_id,
|
||||||
|
async move |provider_id| {
|
||||||
|
let Some(provider_id) = provider_id.get() else {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the scholarships
|
||||||
|
get_provider_scholarships(provider_id).await
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register scholarship creation action
|
||||||
|
let create_action = ServerAction::<RegisterScholarship>::new();
|
||||||
|
|
||||||
|
// Button handlers
|
||||||
|
let create_on_click = move |_| {
|
||||||
|
// In the future, we'd actually like to create/show a dialog that gets the
|
||||||
|
// desired name of the scholarship.
|
||||||
|
// After the request is sent, we'll handle it in an effect.
|
||||||
|
create_action.dispatch(RegisterScholarship {
|
||||||
|
provider_id: provider_id.get().unwrap(),
|
||||||
|
scholarship_name: "Testing Scholarship".to_string()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register scholarship creation effect
|
||||||
|
Effect::new(move || {
|
||||||
|
// We want to wait for the action to complete first, but then find whether the request
|
||||||
|
// succeeded.
|
||||||
|
if let Some(Ok(_)) = create_action.value().get() {
|
||||||
|
// Refetch and clear.
|
||||||
|
scholarships.refetch();
|
||||||
|
create_action.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="w-75">
|
||||||
|
<Panel>
|
||||||
|
<div class="pt-2">
|
||||||
|
<h2 class="text-xl font-bold text-center">"Scholarships"</h2>
|
||||||
|
</div>
|
||||||
|
<Transition fallback=Loading>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{move || {
|
||||||
|
scholarships.get().map(|result| {
|
||||||
|
match result {
|
||||||
|
Ok(list) => {
|
||||||
|
let views = list.iter().map(|entry| {
|
||||||
|
view! { <ScholarshipListEntry scholarship=entry.clone() /> }
|
||||||
|
});
|
||||||
|
Either::Left(views.collect_view())
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
Either::Right(view! {
|
||||||
|
<div>
|
||||||
|
<p>"Failed to load scholarship list: "{err.to_string()}</p>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect_view()
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="p-2 bg-red-700 rounded-md text-center text-white hover:bg-red-800 transition-all"
|
||||||
|
on:click=create_on_click>
|
||||||
|
"Create New"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Panel>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn ScholarshipListEntry(scholarship: ExpandableInfo) -> impl IntoView {
|
||||||
|
let navigate = use_navigate();
|
||||||
|
|
||||||
|
// Get the values that we need out of the info. We just need the subject and the name of
|
||||||
|
// the scholarship.
|
||||||
|
let name = scholarship.data.get("scholarship_name")
|
||||||
|
.unwrap_or(&ValueType::String(None))
|
||||||
|
.as_string().unwrap_or_default()
|
||||||
|
.unwrap_or("<no name found>".to_string());
|
||||||
|
|
||||||
|
// Button click handlers
|
||||||
|
let edit_click = {
|
||||||
|
let subject = scholarship.subject.clone();
|
||||||
|
move |_| {
|
||||||
|
let path = format!("/providers/scholarships/{}", subject);
|
||||||
|
navigate(path.as_str(), Default::default());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let delete_click = {
|
||||||
|
let subject = scholarship.subject.clone();
|
||||||
|
move |_| {
|
||||||
|
// Delete the scholarship. For now, we'll just log it.
|
||||||
|
log!("Deleting scholarship with ID {:?}", subject);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="flex flex-col rounded-md border-light inset-shadow-xs shadow-md my-2 transition-all hover:shadow-lg/33">
|
||||||
|
<div class="p-2 text-center">
|
||||||
|
<span>{name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<div class="p-2 flex-1 bg-red-700 border-r border-white rounded-bl-md text-white text-center
|
||||||
|
hover:bg-red-800 cursor-pointer transition-all"
|
||||||
|
on:click=edit_click
|
||||||
|
>
|
||||||
|
"Edit"
|
||||||
|
</div>
|
||||||
|
<div class="p-2 flex-1 bg-red-700 border-l border-white rounded-br-md text-white text-center
|
||||||
|
hover:bg-red-800 cursor-pointer transition-all"
|
||||||
|
on:click=delete_click
|
||||||
|
>
|
||||||
|
"Delete"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn ScholarshipForm(
|
fn ScholarshipForm(
|
||||||
response: ExpandableInfo,
|
response: ExpandableInfo,
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
use leptos::prelude::{use_context, Signal, With};
|
||||||
|
use leptos_oidc::{Algorithm, AuthSignal, TokenData};
|
||||||
|
use crate::common::UserClaims;
|
||||||
|
|
||||||
/// # Server Utilities
|
/// # Server Utilities
|
||||||
///
|
///
|
||||||
/// These utility functions can only be used from the server. They are created to use server-side
|
/// These utility functions can only be used from the server. They are created to use server-side
|
||||||
@@ -17,3 +21,16 @@ pub mod server_utils {
|
|||||||
aws_sdk_dynamodb::Client::new(&create_aws_config().await)
|
aws_sdk_dynamodb::Client::new(&create_aws_config().await)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the current user claims. This function should only be used in an area that has
|
||||||
|
/// access to an AuthSignal, or it will result in a total failure.
|
||||||
|
pub fn get_user_claims() -> Signal<Option<TokenData<UserClaims>>> {
|
||||||
|
let auth = use_context::<AuthSignal>().expect("Couldn't find AuthSignal.");
|
||||||
|
Signal::derive(move || {
|
||||||
|
auth.with(|auth| {
|
||||||
|
auth.authenticated().and_then(|data| {
|
||||||
|
data.decoded_access_token::<UserClaims>(Algorithm::RS256, &["account"])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user
Add confirmation before deleting a scholarship to prevent accidental deletion.