PageSourceSearch

https://sifrcrm.up.railway.app/assets/controllers/form_recipients_…abac8b5660b17c2465b482004f4bc509fc05126a.js

js sifrcrm.up.railway.app collected 2026-10-03 12:15:40 UTC 3,176 bytes, 95 lines download raw bytes

1// app/javascript/controllers/form_recipients_controller.js
2import { Controller } from "@hotwired/stimulus"
3
4// Manages the list of recipient-email rows on a form.
5// Create/Edit modal: rows submit with the surrounding form (autoSave off).
6// Builder settings drawer: autoSave on — add/remove/blur persist immediately
7// via POST /forms/:id/update_recipients (mirrors hidden_tags_controller).
8export default class extends Controller {
9  static targets = ["template", "container", "row", "saveAlert", "errorAlert"]
10  static values = {
11    formId: String,
12    autoSave: { type: Boolean, default: false }
13  }
14
15  add(event) {
16    event.preventDefault()
17    this.containerTarget.insertAdjacentHTML("beforeend", this.templateTarget.innerHTML)
18    const input = this.containerTarget.lastElementChild?.querySelector("input[type='email']")
19    if (input) input.focus()
20  }
21
22  remove(event) {
23    event.preventDefault()
24    event.currentTarget.closest(".form-recipient-row")?.remove()
25    if (this.autoSaveValue) this.save()
26  }
27
28  save() {
29    if (!this.autoSaveValue) return
30    if (!this.allValid()) {
31      this.showError("Fix the highlighted email address — changes are not saved yet.")
32      return
33    }
34
35    // Snapshot the payload now, but chain the POST behind any in-flight save so
36    // overlapping blur + remove saves can't land out of order on the server.
37    const payload = JSON.stringify({ recipient_emails: this.emails() })
38    this.pendingSave = (this.pendingSave || Promise.resolve()).then(() => this.postRecipients(payload))
39  }
40
41  postRecipients(payload) {
42    return fetch(`/forms/${this.formIdValue}/update_recipients`, {
43      method: "POST",
44      headers: {
45        "Content-Type": "application/json",
46        // Optional chain: csrf_meta_tags renders nothing when forgery protection is off (test env)
47        "X-CSRF-Token": document.querySelector("meta[name='csrf-token']")?.content || "",
48        "Accept": "application/json"
49      },
50      body: payload
51    })
52      .then((response) => response.json())
53      .then((data) => {
54        if (data.success) {
55          this.showSaved()
56        } else {
57          this.showError((data.errors || []).join(", "))
58        }
59      })
60      .catch(() => this.showError("Could not save recipients."))
61  }
62
63  emails() {
64    return this.inputs().map((input) => input.value.trim()).filter((value) => value !== "")
65  }
66
67  inputs() {
68    return Array.from(this.containerTarget.querySelectorAll("input[type='email']"))
69  }
70
71  allValid() {
72    let valid = true
73    this.inputs().forEach((input) => {
74      const ok = input.value.trim() === "" || input.checkValidity()
75      input.classList.toggle("input-error", !ok)
76      if (!ok) valid = false
77    })
78    return valid
79  }
80
81  showSaved() {
82    if (this.hasErrorAlertTarget) this.errorAlertTarget.classList.add("hidden")
83    if (!this.hasSaveAlertTarget) return
84
85    this.saveAlertTarget.classList.remove("hidden")
86    setTimeout(() => this.saveAlertTarget.classList.add("hidden"), 2000)
87  }
88
89  showError(message) {
90    if (!this.hasErrorAlertTarget) return
91
92    this.errorAlertTarget.textContent = message
93    this.errorAlertTarget.classList.remove("hidden")
94  }
95};

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.