-
-
Notifications
You must be signed in to change notification settings - Fork 512
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4937 from danielabar/4472-partner-profile-files-d…
…irect-upload [#4472] Persist file uploads through validation errors
- Loading branch information
Showing
16 changed files
with
231 additions
and
524 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
|
||
// Connects to data-controller="file-input-label" | ||
// | ||
// Reproduces the native browser behavior of updating a file input label | ||
// to show the selected file's name. This is necessary when using a custom | ||
// file input, such as with Bootstrap, that does not update automatically. | ||
// | ||
// Key Features: | ||
// 1. Handles initial display of a default label text (e.g., "Choose file..." or | ||
// the previously selected file name if present). | ||
// 2. Updates the label dynamically when a new file is selected. | ||
// | ||
// How it works: | ||
// - When a file is selected, the `fileSelected` method updates the text of the | ||
// label to reflect the name of the selected file. | ||
// - On page load, the `connect` method ensures the label is initialized to the | ||
// correct state (default text or file name, if a file was previously selected). | ||
// | ||
// This controller is used in coordination with direct uploads in Active Storage. | ||
// When a validation error occurs, previously selected files persist on the server | ||
// (via direct upload), and the file name can be displayed to the user. | ||
export default class extends Controller { | ||
static targets = ["input", "label"]; | ||
static values = { | ||
defaultText: { type: String, default: 'Choose file...' } | ||
} | ||
|
||
connect() { | ||
this.updateLabel(); | ||
} | ||
|
||
updateLabel() { | ||
const input = this.inputTarget; | ||
const label = this.labelTarget; | ||
|
||
// Check if the file input has a file selected | ||
if (input.files.length > 0) { | ||
label.textContent = input.files[0].name; | ||
} else { | ||
label.textContent = this.defaultTextValue; | ||
} | ||
} | ||
|
||
// Update the label when a file is selected | ||
fileSelected() { | ||
this.updateLabel(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<%# locals: (form_builder:, label_for:, label_text:, attachment:, attachment_name:) %> | ||
|
||
<%# Creates a custom file input field with the following features: %> | ||
<%# - Displays the name of a previously selected file (even after validation errors). %> | ||
<%# - Integrates with Active Storage direct uploads to persist the file on the server, %> | ||
<%# even if form submission fails. %> | ||
<%# - Uses a Stimulus controller (`file_input_label`) to dynamically update the label %> | ||
<%# text when a new file is selected. %> | ||
<%# - Styled with Bootstrap's custom file input classes. %> | ||
<%# %> | ||
<%# Arguments: %> | ||
<%# - form_builder: The form builder object. %> | ||
<%# - label_for: The ID of the file input (used for the label `for` attribute). %> | ||
<%# - label_text: The text to display for the file input's label. %> | ||
<%# - attachment: The Active Storage attachment object (used to check for existing files). %> | ||
<%# - attachment_name: The name of the attachment field (e.g., `:proof_of_form_990`). %> | ||
|
||
<div class="form-group"> | ||
<label class="control-label"><%= label_text %></label> | ||
|
||
<% if attachment.persisted? %> | ||
Attached file: <%= link_to attachment.blob.filename, rails_blob_path(attachment), class: "font-weight-bold" %> | ||
<% elsif attachment.attached? %> | ||
<%= form_builder.hidden_field attachment_name, value: attachment.signed_id %> | ||
<% end %> | ||
|
||
<div class="col-md-12" | ||
data-controller="file-input-label" | ||
data-file-input-label-default-text-value="<%= attachment.attached? ? attachment.blob.filename : 'Choose file...' %>"> | ||
<%= form_builder.file_field attachment_name, | ||
direct_upload: true, | ||
class: "custom-file-input", | ||
data: { | ||
action: "change->file-input-label#fileSelected", | ||
file_input_label_target: "input" | ||
} %> | ||
<label class="custom-file-label" | ||
for="<%= label_for %>" | ||
data-file-input-label-target="label">Choose file...</label> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,3 +33,4 @@ | |
pin "filterrific", to: "filterrific.js" | ||
pin "bootstrap-select", to: "https://ga.jspm.io/npm:[email protected]/dist/js/bootstrap-select.js" | ||
pin "jquery-ui", to: "https://ga.jspm.io/npm:[email protected]/ui/widget.js" | ||
pin "@rails/activestorage", to: "@rails--activestorage.js" # @8.0.100 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,132 +11,18 @@ | |
<link href="https://cdn.jsdelivr.net/npm/[email protected]/build/toastr.css" rel="stylesheet"> | ||
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css" rel="stylesheet"> | ||
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/v4-shims.css" rel="stylesheet"> | ||
|
||
<link rel="stylesheet" href="/assets/application.css" media="all" /> | ||
<script type="importmap" data-turbo-track="reload">{ | ||
"imports": { | ||
"jquery": "https://ga.jspm.io/npm:[email protected]/dist/jquery.js", | ||
"admin-lte": "/assets/adminlte.js", | ||
"application": "/assets/application.js", | ||
"startup": "/assets/startup.js", | ||
"@hotwired/turbo-rails": "/assets/turbo.min.js", | ||
"@hotwired/stimulus": "/assets/stimulus.min.js", | ||
"@hotwired/stimulus-loading": "/assets/stimulusloading.js", | ||
"bootstrap": "/assets/bootstrap.min.js", | ||
"popper": "/assets/popper.js", | ||
"highcharts": "https://ga.jspm.io/npm:[email protected]/highcharts.js", | ||
"select2": "https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js", | ||
"trix": "https://ga.jspm.io/npm:[email protected]/dist/trix.esm.min.js", | ||
"@rails/actiontext": "https://ga.jspm.io/npm:@rails/[email protected]/app/assets/javascripts/actiontext.js", | ||
"luxon": "https://ga.jspm.io/npm:[email protected]/build/cjs-browser/luxon.js", | ||
"litepicker": "https://cdn.jsdelivr.net/npm/litepicker/dist/litepicker.js", | ||
"litepicker/ranges": "https://cdn.jsdelivr.net/npm/litepicker/dist/plugins/ranges.js", | ||
"toastr": "https://ga.jspm.io/npm:[email protected]/toastr.js", | ||
"@fullcalendar/core": "https://ga.jspm.io/npm:@fullcalendar/[email protected]/index.js", | ||
"preact": "https://ga.jspm.io/npm:[email protected]/dist/preact.module.js", | ||
"preact/compat": "https://ga.jspm.io/npm:[email protected]/compat/dist/compat.module.js", | ||
"preact/hooks": "https://ga.jspm.io/npm:[email protected]/hooks/dist/hooks.module.js", | ||
"@fullcalendar/luxon": "https://ga.jspm.io/npm:@fullcalendar/[email protected]/index.js", | ||
"@fullcalendar/core/": "https://ga.jspm.io/npm:@fullcalendar/[email protected]/", | ||
"@fullcalendar/daygrid": "https://ga.jspm.io/npm:@fullcalendar/[email protected]/index.js", | ||
"@fullcalendar/list": "https://ga.jspm.io/npm:@fullcalendar/[email protected]/index.js", | ||
"quagga": "https://ga.jspm.io/npm:[email protected]/dist/quagga.min.js", | ||
"@rails/ujs": "https://ga.jspm.io/npm:@rails/[email protected]/lib/assets/compiled/rails-ujs.js", | ||
"filterrific": "/assets/filterrific.js", | ||
"bootstrap-select": "https://ga.jspm.io/npm:[email protected]/dist/js/bootstrap-select.js", | ||
"jquery-ui": "https://ga.jspm.io/npm:[email protected]/ui/widget.js", | ||
"controllers/application": "/assets/controllers/application.js", | ||
"controllers/area_served_controller": "/assets/controllers/area_served_controller.js", | ||
"controllers/checkbox_with_nested_element_controller": "/assets/controllers/checkbox_with_nested_element_controller.js", | ||
"controllers/confirmation_controller": "/assets/controllers/confirmation_controller.js", | ||
"controllers/distribution_delivery_controller": "/assets/controllers/distribution_delivery_controller.js", | ||
"controllers/double_select_controller": "/assets/controllers/double_select_controller.js", | ||
"controllers/form_input_controller": "/assets/controllers/form_input_controller.js", | ||
"controllers/highchart_controller": "/assets/controllers/highchart_controller.js", | ||
"controllers": "/assets/controllers/index.js", | ||
"controllers/item_units_controller": "/assets/controllers/item_units_controller.js", | ||
"controllers/password_visibility_controller": "/assets/controllers/password_visibility_controller.js", | ||
"controllers/select2_controller": "/assets/controllers/select2_controller.js", | ||
"controllers/served_area_controller": "/assets/controllers/served_area_controller.js", | ||
"controllers/turbo_controller": "/assets/controllers/turbo_controller.js", | ||
"utils/barcode_items": "/assets/utils/barcode_items.js", | ||
"utils/barcode_scan": "/assets/utils/barcode_scan.js", | ||
"utils/deadline_day_pickers": "/assets/utils/deadline_day_pickers.js", | ||
"utils/distributions_and_transfers": "/assets/utils/distributions_and_transfers.js", | ||
"utils/donations": "/assets/utils/donations.js", | ||
"utils/purchases": "/assets/utils/purchases.js" | ||
} | ||
}</script> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/dist/jquery.js"> | ||
<link rel="modulepreload" href="/assets/adminlte.js"> | ||
<link rel="modulepreload" href="/assets/application.js"> | ||
<link rel="modulepreload" href="/assets/startup.js"> | ||
<link rel="modulepreload" href="/assets/turbo.min.js"> | ||
<link rel="modulepreload" href="/assets/stimulus.min.js"> | ||
<link rel="modulepreload" href="/assets/stimulusloading.js"> | ||
<link rel="modulepreload" href="/assets/bootstrap.min.js"> | ||
<link rel="modulepreload" href="/assets/popper.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/highcharts.js"> | ||
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/dist/trix.esm.min.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:@rails/[email protected]/app/assets/javascripts/actiontext.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/build/cjs-browser/luxon.js"> | ||
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/litepicker/dist/litepicker.js"> | ||
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/litepicker/dist/plugins/ranges.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/toastr.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:@fullcalendar/[email protected]/index.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/dist/preact.module.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/compat/dist/compat.module.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/hooks/dist/hooks.module.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:@fullcalendar/[email protected]/index.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:@fullcalendar/[email protected]/"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:@fullcalendar/[email protected]/index.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:@fullcalendar/[email protected]/index.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/dist/quagga.min.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:@rails/[email protected]/lib/assets/compiled/rails-ujs.js"> | ||
<link rel="modulepreload" href="/assets/filterrific.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/dist/js/bootstrap-select.js"> | ||
<link rel="modulepreload" href="https://ga.jspm.io/npm:[email protected]/ui/widget.js"> | ||
<link rel="modulepreload" href="/assets/controllers/application.js"> | ||
<link rel="modulepreload" href="/assets/controllers/area_served_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/checkbox_with_nested_element_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/confirmation_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/distribution_delivery_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/double_select_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/form_input_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/highchart_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/index.js"> | ||
<link rel="modulepreload" href="/assets/controllers/item_units_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/password_visibility_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/select2_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/served_area_controller.js"> | ||
<link rel="modulepreload" href="/assets/controllers/turbo_controller.js"> | ||
<link rel="modulepreload" href="/assets/utils/barcode_items.js"> | ||
<link rel="modulepreload" href="/assets/utils/barcode_scan.js"> | ||
<link rel="modulepreload" href="/assets/utils/deadline_day_pickers.js"> | ||
<link rel="modulepreload" href="/assets/utils/distributions_and_transfers.js"> | ||
<link rel="modulepreload" href="/assets/utils/donations.js"> | ||
<link rel="modulepreload" href="/assets/utils/purchases.js"> | ||
<script type="module">import "application"</script> | ||
|
||
<script type="esms-options"> | ||
{ | ||
"noLoadEventRetriggers": true | ||
} | ||
</script> | ||
|
||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> | ||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> | ||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> | ||
<link rel="manifest" href="/site.webmanifest"> | ||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"> | ||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic"> | ||
|
||
<meta name="turbo-visit-control" content="reload"> | ||
<meta name="turbo-cache-control" content="no-cache"> | ||
<meta name="turbo-visit-control" content="reload"> | ||
<meta name="turbo-cache-control" content="no-cache"> | ||
</head> | ||
<body data-turbo="" data-controller='turbo' | ||
id="errors" class="not_found hold-transition sidebar-mini layout-fixed"> | ||
|
||
<body id="errors" class="not_found hold-transition sidebar-mini layout-fixed"> | ||
<!-- Site wrapper --> | ||
<div class="wrapper"> | ||
<nav class="main-header navbar navbar-expand navbar-white navbar-light"> | ||
|
@@ -188,7 +74,7 @@ <h1>403 Error Page</h1> | |
<!-- Main content --> | ||
<section class="content"> | ||
<div class="error-page"> | ||
<h2 class="headline text-warning"> 403</h2> | ||
<h2 class="headline text-warning">403</h2> | ||
<br> | ||
<div class="error-content"> | ||
<h3><i class="fas fa-exclamation-triangle text-warning"></i> Oops! The page you were looking for is forbidden.</h3> | ||
|
Oops, something went wrong.