PageSourceSearch

https://cultureg.fr/assets/controllers/buy_configurator_controller-554447b2.js

js cultureg.fr collected 2026-10-05 19:12:15 UTC 5,268 bytes, 123 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// Drives the single configurable buy card on /pricing. Two toggles reconfigure the
4// whole card between three states:
5//
6//   fresh OFF, verified OFF -> Full Database CSV, $149 raw  (default)
7//   fresh OFF, verified ON  -> Full Database CSV, $248 one-time
8//   fresh ON                -> StoreRadar Annual, $399/yr (verified forced "Included")
9//
10// All copy lives in the ERB (so prices/counts stay server-rendered and em-dash-free);
11// this controller only shows/hides the per-state groups and computes the live total.
12// Prices and endpoints come from data values, never hardcoded here.
13//
14//   data-controller="buy-configurator"
15//   data-buy-configurator-csv-price-value / addon-price-value / annual-price-value
16//   data-buy-configurator-csv-url-value / annual-url-value
17//   toggles:  target="fresh" / "verified"  (hidden checkboxes, change->buy-configurator#update)
18//   groups:   data-when="csv|annual|verified|raw|verified-any"  (shown per state)
19//             verified = CSV + ticked (the price breakdown); verified-any = verification
20//             included at all, so it covers Annual too.
21//   dynamic:  target="price" / "button" / "fill"
22export default class extends Controller {
23  static targets = ["fresh", "verified", "price", "button", "fill"]
24  static values = {
25    csvPrice: Number,
26    addonPrice: Number,
27    annualPrice: Number,
28    csvUrl: String,
29    annualUrl: String
30  }
31
32  connect() {
33    this.onHashChange = this.onHashChange.bind(this)
34    window.addEventListener("hashchange", this.onHashChange)
35    this.applyHash() // honor a #buy / #buy-annual deep link on load
36    this.update()
37    this.scrollToDeepLink() // cross-page /pricing#buy: scroll the card into view on arrival
38  }
39
40  disconnect() {
41    window.removeEventListener("hashchange", this.onHashChange)
42  }
43
44  // A buy-intent CTA that scrolls here can request a state: #buy-annual opens the card
45  // in the StoreRadar Annual state, #buy opens it as the one-time CSV. Fires on load and
46  // on same-page hash changes (an in-page anchor click doesn't reconnect the controller).
47  onHashChange() {
48    this.applyHash()
49    this.update()
50  }
51
52  applyHash() {
53    if (!this.hasFreshTarget) return
54    const hash = window.location.hash
55    if (hash === "#buy-annual") this.freshTarget.checked = true
56    else if (hash === "#buy") this.freshTarget.checked = false
57  }
58
59  // A cross-page buy-intent CTA (/pricing#buy[-annual]), a reload, or a bookmark lands here
60  // with the hash already set. Native fragment scroll into the overflow-auto #main-content is
61  // unreliable (esp. after a Turbo visit), so we scroll the card in ourselves; rAF lets layout
62  // settle first. Same-page clicks don't reconnect the card, so this only runs on real arrivals.
63  scrollToDeepLink() {
64    const hash = window.location.hash
65    if (hash !== "#buy" && hash !== "#buy-annual") return
66    const target = document.getElementById(hash.slice(1))
67    if (target) requestAnimationFrame(() => target.scrollIntoView({ behavior: "smooth", block: "start" }))
68  }
69
70  update() {
71    const annual = this.hasFreshTarget && this.freshTarget.checked
72    // Annual always includes verification; lock the verified toggle on while it's active.
73    if (this.hasVerifiedTarget) {
74      if (annual) {
75        this.verifiedTarget.checked = true
76        this.verifiedTarget.disabled = true
77      } else {
78        this.verifiedTarget.disabled = false
79      }
80    }
81    const verified = annual || (this.hasVerifiedTarget && this.verifiedTarget.checked)
82
83    // Per-state content groups. Toggle the `hidden` class (not the attribute) so it wins
84    // over any display utility; toggled elements carry no competing display class.
85    this.show('[data-when="csv"]', !annual)
86    this.show('[data-when="annual"]', annual)
87    this.show('[data-when="verified"]', !annual && verified)
88    this.show('[data-when="verified-any"]', verified)
89    this.show('[data-when="raw"]', !annual && !verified)
90
91    // Price + live total.
92    const total = annual ? this.annualPriceValue : this.csvPriceValue + (verified ? this.addonPriceValue : 0)
93    if (this.hasPriceTarget) this.priceTarget.textContent = `$${total}`
94
95    // Freshness bar: a sliver of orange for the 60-day window, full orange for the year.
96    if (this.hasFillTarget) {
97      this.fillTarget.style.width = annual ? "100%" : "16%"
98      this.fillTarget.classList.toggle("bg-orange-600", annual)
99      this.fillTarget.classList.toggle("bg-orange-300", !annual)
100    }
101
102    // CTA label + destination.
103    if (this.hasButtonTarget) {
104      if (annual) {
105        this.buttonTarget.textContent = `Go Annual for $${this.annualPriceValue}/yr`
106        this.buttonTarget.href = this.annualUrlValue
107      } else {
108        this.buttonTarget.textContent = `Buy the CSV for $${total}`
109        try {
110          const url = new URL(this.csvUrlValue, window.location.origin)
111          url.searchParams.set("verified", verified ? "true" : "false")
112          this.buttonTarget.href = url.pathname + url.search
113        } catch (_e) {
114          this.buttonTarget.href = this.csvUrlValue
115        }
116      }
117    }
118  }
119
120  show(selector, on) {
121    this.element.querySelectorAll(selector).forEach((el) => el.classList.toggle("hidden", !on))
122  }
123}

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.