1import { Controller } from "@hotwired/stimulus" 2 3export default class extends Controller { 4 5 submit(event) { 6 event.preventDefault() 7 8 const form = event.currentTarget 9 const submitBtn = form.querySelector('input[type="submit"], button[type="submit"]') 10 const originalBtnText = submitBtn ? submitBtn.value : "Submit" 11 12 if (submitBtn) { 13 submitBtn.disabled = true 14 submitBtn.value = "Submitting..." 15 } 16 17 fetch(form.action, { 18 method: "POST", 19 headers: { 20 "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content, 21 "Accept": "application/json" 22 }, 23 body: new FormData(form) 24 }) 25 .then(response => { 26 if (!response.ok) throw new Error("Network response was not ok") 27 return response.json() 28 }) 29 .then(data => { 30 this.closeModal() 31 this.showToast(data.message || "Report submitted successfully", "success") 32 form.reset() 33 }) 34 .catch(error => { 35 console.error("Error:", error) 36 this.showToast("Failed to submit report. Please try again.", "error") 37 }) 38 .finally(() => { 39 if (submitBtn) { 40 submitBtn.disabled = false 41 submitBtn.value = originalBtnText 42 } 43 }) 44 } 45 46 closeModal() { 47 // Find the close button in the specific modal instance and click it 48 // This assumes the form is inside the modal 49 const modalContent = this.element.closest('.modal-content') 50 if (modalContent) { 51 const closeBtn = modalContent.querySelector('.close-modal-btn') 52 if (closeBtn) closeBtn.click() 53 } 54 } 55 56 showToast(message, type) { 57 // Check if toast container exists 58 let container = document.getElementById("toast-container") 59 if (!container) { 60 container = document.createElement("div") 61 container.id = "toast-container" 62 container.style.cssText = "position: fixed; bottom: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 10px;" 63 document.body.appendChild(container) 64 } 65 66 const toast = document.createElement("div") 67 toast.textContent = message 68 // Base styles 69 Object.assign(toast.style, { 70 padding: "12px 24px", 71 borderRadius: "8px", 72 color: "white", 73 fontWeight: "500", 74 backgroundColor: type === 'success' ? '#22c55e' : '#ef4444', // Green or Red 75 boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", 76 transition: "all 0.3s ease", 77 opacity: "0", 78 transform: "translateY(20px)", 79 fontSize: "14px", 80 minWidth: "300px" 81 }) 82 83 container.appendChild(toast) 84 85 // Animate in 86 requestAnimationFrame(() => { 87 toast.style.opacity = "1" 88 toast.style.transform = "translateY(0)" 89 }) 90 91 // Remove after 3s 92 setTimeout(() => { 93 toast.style.opacity = "0" 94 toast.style.transform = "translateY(20px)" 95 setTimeout(() => toast.remove(), 300) 96 }, 3000) 97 } 98}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.