Implement checking for file types #63

Merged
BobdaFett merged 1 commits from feat/file-type-restriction into main 2026-03-03 23:20:30 +00:00
2 changed files with 62 additions and 12 deletions

No files matched your search

+56 -11
View File
@@ -9,9 +9,19 @@ use leptos_use::{UseDropZoneOptions, UseDropZoneReturn, use_drop_zone_with_optio
#[component] #[component]
pub fn FileDrop( pub fn FileDrop(
#[prop(into)] name: String, /// The name of this input. Used in file organization.
#[prop(into)] form_id: String, #[prop(into)]
#[prop()] existing_files: Resource<Result<Vec<String>, ServerFnError>>, name: String,
/// The ID of the form containing this input. Used in file organization.
#[prop(into)]
form_id: String,
/// The types that a file is allowed to be. Must be specified as the desired file extension (i.e.
/// `.pdf`, `.txt`, etc.)
#[prop(optional)]
allowed_types: Vec<String>,
/// A [`Resource`] that gives the currently uploaded files for this input.
#[prop()]
existing_files: Resource<Result<Vec<String>, ServerFnError>>,
) -> impl IntoView { ) -> impl IntoView {
let zone_ref = NodeRef::<Label>::new(); let zone_ref = NodeRef::<Label>::new();
let input_ref = NodeRef::<Input>::new(); let input_ref = NodeRef::<Input>::new();
@@ -32,6 +42,22 @@ pub fn FileDrop(
let uploading = upload_action.pending(); let uploading = upload_action.pending();
let deleting = delete_action.pending(); let deleting = delete_action.pending();
let verify_file_type = move |file_name: String| {
if !allowed_types.is_empty() {
let verified = allowed_types
.iter()
.any(|file_ext| file_name.ends_with(file_ext));
if !verified {
debug_log!("Blocking file with name {file_name}");
} else {
debug_log!("Allowing file with name {file_name}");
}
verified
} else {
true
}
};
let upload_files = Callback::new(move |files: Vec<File>| { let upload_files = Callback::new(move |files: Vec<File>| {
let form_data = FormData::new().unwrap(); let form_data = FormData::new().unwrap();
@@ -69,9 +95,10 @@ pub fn FileDrop(
UseDropZoneOptions::default() UseDropZoneOptions::default()
.on_over(move |_| hovering.set(true)) .on_over(move |_| hovering.set(true))
.on_drop(move |ev| { .on_drop(move |ev| {
let file = ev.files.first().unwrap(); debug_log!("Received dropped file. List has length {}", ev.files.len());
ev.files
upload_files.run(vec![file.to_owned()]); .iter()
.for_each(|file| upload_files.run(vec![file.to_owned()]));
}), }),
); );
@@ -90,7 +117,20 @@ pub fn FileDrop(
}) })
.unwrap_or_default(); .unwrap_or_default();
upload_files.run(files); let verified_files = files
.iter()
.filter_map(|file| verify_file_type(file.name().to_string()).then(|| file.to_owned()))
.collect::<Vec<File>>();
// input_ref.try_update(|input| {
// if let Some(input) = input {
// input.set_files(None);
// }
// });
if !verified_files.is_empty() {
upload_files.run(verified_files);
}
}; };
let on_click_file_delete = move |file_name: String| { let on_click_file_delete = move |file_name: String| {
@@ -103,14 +143,17 @@ pub fn FileDrop(
input_name: name.get_value(), input_name: name.get_value(),
access_token: access_token.unwrap_or_default(), access_token: access_token.unwrap_or_default(),
file_name, file_name,
}) });
}; };
// Add file names to the list when upload_action is successful. // Add file names to the list when upload_action is successful.
Effect::new(move || { Effect::new(move || {
if let Some(Ok(uploaded_file_name)) = upload_action.value().get() { if let Some(Ok(uploaded_file_name)) = upload_action.value().get() {
file_name_list.update(|list| { file_name_list.update(|list| {
list.push(uploaded_file_name); list.iter()
.find(|v| **v == uploaded_file_name)
.is_none()
.then(|| list.push(uploaded_file_name));
}); });
} }
}); });
@@ -134,7 +177,6 @@ pub fn FileDrop(
} }
}) })
}} }}
<label <label
node_ref=zone_ref node_ref=zone_ref
class="m-1.5 p-2.5 flex flex-col transition-color duration-200 rounded-lg border-2 items-center cursor-pointer" class="m-1.5 p-2.5 flex flex-col transition-color duration-200 rounded-lg border-2 items-center cursor-pointer"
@@ -163,7 +205,10 @@ pub fn FileDrop(
view! { view! {
<div class="m-1.5 flex flex-row relative items-center"> <div class="m-1.5 flex flex-row relative items-center">
<div class="flex-1">{file.get_value()}</div> <div class="flex-1">{file.get_value()}</div>
<div class="text-gray-400 hover:text-red-700 right-0" on:click=on_click> <div
class="text-gray-400 hover:text-red-700 right-0 cursor-pointer"
on:click=on_click
>
<Icon icon=icondata::FaTrashCanRegular /> <Icon icon=icondata::FaTrashCanRegular />
</div> </div>
</div> </div>
+6 -1
View File
@@ -34,7 +34,12 @@ pub fn StudentFinancialPage() -> impl IntoView {
description="Please upload your FAFSA SAR (Student Aid Report) here." description="Please upload your FAFSA SAR (Student Aid Report) here."
/> />
<Suspense fallback=Loading> <Suspense fallback=Loading>
<FileDrop name="fafsa" form_id="financial_info" existing_files=files_resource /> <FileDrop
name="fafsa"
form_id="financial_info"
existing_files=files_resource
allowed_types=vec![".pdf".to_string()]
/>
</Suspense> </Suspense>
// </ValidatedForm> // </ValidatedForm>
</div> </div>