import { Controller } from "@hotwired/stimulus" import { DirectUpload } from "@rails/activestorage" import { showFlashMessage } from "../maps/helpers" export default class extends Controller { static targets = ["input", "progress", "progressBar", "submit", "form"] static values = { url: String, userTrial: Boolean, currentImportsCount: Number } connect() { this.inputTarget.addEventListener("change", this.upload.bind(this)) // Add form submission handler to disable the file input if (this.hasFormTarget) { this.formTarget.addEventListener("submit", this.onSubmit.bind(this)) } // Initially disable submit button if no files are uploaded if (this.hasSubmitTarget) { const hasUploadedFiles = this.element.querySelectorAll('input[name="import[files][]"][type="hidden"]').length > 0 this.submitTarget.disabled = !hasUploadedFiles } } onSubmit(event) { if (this.isUploading) { // If still uploading, prevent submission event.preventDefault() return } // Disable the file input to prevent it from being submitted with the form // This ensures only our hidden inputs with signed IDs are submitted this.inputTarget.disabled = true // Check if we have any signed IDs const signedIds = this.element.querySelectorAll('input[name="import[files][]"][type="hidden"]') if (signedIds.length === 0) { event.preventDefault() alert("Please select and upload files first") } else { console.log(`Submitting form with ${signedIds.length} uploaded files`) } } upload() { const files = this.inputTarget.files if (files.length === 0) return // Check import count limits for trial users if (this.userTrialValue && this.currentImportsCountValue >= 5) { const message = 'Import limit reached. Trial users can only create up to 5 imports. Please subscribe to import more files.' showFlashMessage('error', message) // Clear the file input this.inputTarget.value = '' return } // Check file size limits for trial users if (this.userTrialValue) { const MAX_FILE_SIZE = 11 * 1024 * 1024 // 11MB in bytes const oversizedFiles = Array.from(files).filter(file => file.size > MAX_FILE_SIZE) if (oversizedFiles.length > 0) { const fileNames = oversizedFiles.map(f => f.name).join(', ') const message = `File size limit exceeded. Trial users can only upload files up to 10MB. Oversized files: ${fileNames}` showFlashMessage('error', message) // Clear the file input this.inputTarget.value = '' return } } this.isUploading = true // Disable submit button during upload this.submitTarget.disabled = true this.submitTarget.classList.add("opacity-50", "cursor-not-allowed") // Show uploading message using flash showFlashMessage('notice', `Uploading ${files.length} files, please wait...`) // Always remove any existing progress bar to ensure we create a fresh one if (this.hasProgressTarget) { this.progressTarget.remove() } // Create a wrapper div with better DaisyUI styling const progressWrapper = document.createElement("div") progressWrapper.className = "w-full mt-4 mb-4" // Add a label with better typography const progressLabel = document.createElement("div") progressLabel.className = "text-sm font-medium text-base-content mb-2 flex justify-between items-center" progressLabel.innerHTML = ` Upload Progress 0% ` progressWrapper.appendChild(progressLabel) // Create DaisyUI progress element const progressContainer = document.createElement("progress") progressContainer.setAttribute("data-direct-upload-target", "progress") progressContainer.className = "progress progress-primary w-full h-3" progressContainer.value = 0 progressContainer.max = 100 // Create a hidden div for the progress bar target (for compatibility) const progressBarFill = document.createElement("div") progressBarFill.setAttribute("data-direct-upload-target", "progressBar") progressBarFill.style.display = "none" progressWrapper.appendChild(progressContainer) progressWrapper.appendChild(progressBarFill) // Add the progress wrapper AFTER the file input field but BEFORE the submit button this.submitTarget.parentNode.insertBefore(progressWrapper, this.submitTarget) let uploadCount = 0 const totalFiles = files.length // Clear any existing hidden fields for files this.element.querySelectorAll('input[name="import[files][]"][type="hidden"]').forEach(el => { if (el !== this.inputTarget) { el.remove() } }); Array.from(files).forEach(file => { const upload = new DirectUpload(file, this.urlValue, this) upload.create((error, blob) => { uploadCount++ if (error) { showFlashMessage('error', `Error uploading ${file.name}: ${error.message || 'Unknown error'}`) } else { // Create a hidden field with the correct name const hiddenField = document.createElement("input") hiddenField.setAttribute("type", "hidden") hiddenField.setAttribute("name", "import[files][]") hiddenField.setAttribute("value", blob.signed_id) this.element.appendChild(hiddenField) } // Enable submit button when all uploads are complete if (uploadCount === totalFiles) { // Only enable submit if we have at least one successful upload const successfulUploads = this.element.querySelectorAll('input[name="import[files][]"][type="hidden"]').length this.submitTarget.disabled = successfulUploads === 0 this.submitTarget.classList.toggle("opacity-50", successfulUploads === 0) this.submitTarget.classList.toggle("cursor-not-allowed", successfulUploads === 0) if (successfulUploads === 0) { showFlashMessage('error', 'No files were successfully uploaded. Please try again.') } else { showFlashMessage('notice', `${successfulUploads} file(s) uploaded successfully. Ready to submit.`) // Add a completion animation to the progress bar const percentageDisplay = this.element.querySelector('.progress-percentage') if (percentageDisplay) { percentageDisplay.textContent = '100%' percentageDisplay.classList.add('text-success') } if (this.hasProgressTarget) { this.progressTarget.value = 100 this.progressTarget.classList.add('progress-success') this.progressTarget.classList.remove('progress-primary') } } this.isUploading = false } }) }) } directUploadWillStoreFileWithXHR(request) { request.upload.addEventListener("progress", event => { if (!this.hasProgressTarget) { console.warn("Progress target not found") return } const progress = (event.loaded / event.total) * 100 const progressPercentage = `${progress.toFixed(1)}%` console.log(`Upload progress: ${progressPercentage}`) // Update the DaisyUI progress element this.progressTarget.value = progress // Update the percentage display const percentageDisplay = this.element.querySelector('.progress-percentage') if (percentageDisplay) { percentageDisplay.textContent = progressPercentage } }) } }