PageSourceSearch

https://humblebaron.com/barons-club/club.js

js humblebaron.com collected 2026-09-29 02:40:29 UTC 13,362 bytes, 403 lines download raw bytes

1(() => {
2  "use strict";
3
4  const openState = document.querySelector("[data-open-state]");
5  const waitlistState = document.querySelector("[data-waitlist-state]");
6  const membershipLabel = document.querySelector("[data-membership-label]");
7  const checkoutButton = document.querySelector("[data-checkout-button]");
8  const checkoutLabel = document.querySelector("[data-checkout-label]");
9  const checkoutError = document.querySelector("[data-checkout-error]");
10  const checkoutDialog = document.querySelector("[data-checkout-dialog]");
11  const dialogTitle = checkoutDialog?.querySelector("[data-dialog-title]");
12  const dialogMessage = checkoutDialog?.querySelector("[data-dialog-message]");
13  const billingInputs = [
14    ...document.querySelectorAll('input[name="billing-plan"]'),
15  ];
16  const priceAmount = document.querySelector("[data-price-amount]");
17  const priceDetail = document.querySelector("[data-price-detail]");
18  const billingFact = document.querySelector("[data-billing-fact]");
19  const renewalFact = document.querySelector("[data-renewal-fact]");
20  const checkoutNote = document.querySelector("[data-checkout-note]");
21  const waitlistForm = document.querySelector("[data-waitlist-form]");
22  const waitlistMessage = document.querySelector("[data-waitlist-message]");
23  const waitlistLabel = document.querySelector("[data-waitlist-label]");
24  const portalLinks = [...document.querySelectorAll("[data-portal-link]")];
25  const mobileActionBar = document.querySelector(".mobile-action-bar");
26  const membershipOffer = document.querySelector("#membership-offer");
27  const stateOverride = new URLSearchParams(window.location.search).get("state");
28  const isStaticPreview =
29    ["localhost", "127.0.0.1"].includes(window.location.hostname) &&
30    window.location.port === "4173";
31
32  let checkoutReady = false;
33  let selectedBillingPlan = "annual";
34  let turnstileWidgetId = null;
35  let turnstileSiteKey = "";
36
37  const billingPlans = {
38    annual: {
39      amount: "500",
40      detail:
41        "billed once per year<br />plus applicable taxes<br />and processing fees",
42      billing: "One annual payment",
43      renewal: "Automatic annually",
44      note:
45        "Complete enrollment without leaving Humble Baron. Applicable taxes and the separate processing fee are shown before payment.",
46    },
47    monthly: {
48      amount: "41.67",
49      detail:
50        "per month<br />12-month commitment<br />plus taxes and processing fees",
51      billing: "11 × $41.67 · final $41.63",
52      renewal: "New term offered after month 12",
53      note:
54        "The monthly plan totals exactly $500: 11 payments of $41.67 and a final payment of $41.63. Applicable taxes and the separate $5 processing fee are added to each payment.",
55    },
56  };
57
58  function setHidden(element, hidden) {
59    if (!element) return;
60    element.hidden = hidden;
61  }
62
63  function setMembershipState(isOpen) {
64    setHidden(openState, !isOpen);
65    setHidden(waitlistState, isOpen);
66    document.body.dataset.membershipState = isOpen ? "open" : "waitlist";
67  }
68
69  function setPortalUrl(url) {
70    if (!url || !url.startsWith("https://")) return;
71    portalLinks.forEach((link) => {
72      link.href = url;
73      link.target = "_blank";
74      link.rel = "noopener noreferrer";
75    });
76  }
77
78  function setBillingPlan(plan) {
79    if (!billingPlans[plan]) return;
80    selectedBillingPlan = plan;
81    const selected = billingPlans[plan];
82
83    if (priceAmount) priceAmount.textContent = selected.amount;
84    if (priceDetail) priceDetail.innerHTML = selected.detail;
85    if (billingFact) billingFact.textContent = selected.billing;
86    if (renewalFact) renewalFact.textContent = selected.renewal;
87    if (checkoutNote) checkoutNote.textContent = selected.note;
88    clearCheckoutError();
89  }
90
91  billingInputs.forEach((input) => {
92    input.addEventListener("change", () => {
93      if (input.checked) setBillingPlan(input.value);
94    });
95  });
96
97  function showDialog({
98    title = "One final connection.",
99    message = "Stripe checkout is being connected for launch. The Club page is ready to preview, but no payment can be collected yet.",
100  }
100 = {}) {
101    if (!checkoutDialog) return;
102    if (dialogTitle) dialogTitle.textContent = title;
103    if (dialogMessage) dialogMessage.textContent = message;
104
105    if (typeof checkoutDialog.showModal === "function") {
106      checkoutDialog.showModal();
107    } else {
108      checkoutDialog.setAttribute("open", "");
109    }
110  }
111
112  function closeDialog() {
113    checkoutDialog?.close?.();
114    checkoutDialog?.removeAttribute("open");
115  }
116
117  checkoutDialog
118    ?.querySelector("[data-dialog-close]")
119    ?.addEventListener("click", closeDialog);
120
121  checkoutDialog?.addEventListener("click", (event) => {
122    if (event.target === checkoutDialog) closeDialog();
123  });
124
125  function showCheckoutError(message) {
126    if (!checkoutError) return;
127    checkoutError.textContent = message;
128    checkoutError.hidden = false;
129  }
130
131  function clearCheckoutError() {
132    if (!checkoutError) return;
133    checkoutError.textContent = "";
134    checkoutError.hidden = true;
135  }
136
137  function setCheckoutLoading(isLoading) {
138    if (!checkoutButton) return;
139    checkoutButton.disabled = isLoading;
140    checkoutButton.classList.toggle("is-loading", isLoading);
141    checkoutButton.setAttribute("aria-busy", String(isLoading));
142    if (checkoutLabel) {
143      checkoutLabel.textContent = isLoading
144        ? "Preparing enrollment"
145        : "Join the Membership";
146    }
147  }
148
149  async function requestJson(url, options = {}) {
150    const controller = new AbortController();
151    const timeout = window.setTimeout(() => controller.abort(), 12_000);
152
153    try {
154      const response = await fetch(url, {
155        ...options,
156        signal: controller.signal,
157        headers: {
158          "content-type": "application/json",
159          ...(options.headers || {}),
160        },
161      });
162      const payload = await response.json().catch(() => ({}));
163      return { response, payload };
164    } finally {
165      window.clearTimeout(timeout);
166    }
167  }
168
169  function loadTurnstile(siteKey) {
170    const container = document.querySelector("[data-turnstile-container]");
171    if (!container || !siteKey || turnstileWidgetId !== null) return;
172
173    turnstileSiteKey = siteKey;
174    const renderWidget = () => {
175      if (!window.turnstile || turnstileWidgetId !== null) return;
176      turnstileWidgetId = window.turnstile.render(container, {
177        sitekey: turnstileSiteKey,
178        theme: "dark",
179      });
180    };
181
182    if (window.turnstile) {
183      renderWidget();
184      return;
185    }
186
187    const script = document.createElement("script");
188    script.src =
189      "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
190    script.async = true;
191    script.defer = true;
192    script.addEventListener("load", renderWidget);
193    script.addEventListener("error", () => {
194      container.textContent =
195        "Spam protection could not load. Please refresh and try again.";
196    });
197    document.head.appendChild(script);
198  }
199
200  async function loadMembershipStatus() {
201    if (!openState && !waitlistState && !portalLinks.length) return;
202
203    if (stateOverride === "waitlist") {
204      setMembershipState(false);
205    } else {
206      setMembershipState(true);
207    }
208
209    if (isStaticPreview) {
210      checkoutReady = false;
211      document.body.dataset.membershipPreview = "true";
212      return;
213    }
214
215    try {
216      const { response, payload } = await requestJson(
217        "/api/membership/status",
218        { method: "GET", headers: {} },
219      );
220      if (!response.ok) throw new Error("Status unavailable");
221
222      checkoutReady = Boolean(payload.checkoutReady);
223      const isOpen =
224        stateOverride === "waitlist"
225          ? false
226          : stateOverride === "open"
227            ? true
228            : Boolean(payload.membershipOpen);
229
230      setMembershipState(isOpen);
231      setPortalUrl(payload.portalUrl);
232      if (payload.turnstileSiteKey) loadTurnstile(payload.turnstileSiteKey);
233
234      if (membershipLabel) {
235        membershipLabel.textContent = isOpen
236          ? "Membership is open"
237          : "Membership is at capacity";
238      }
239    } catch {
240      checkoutReady = false;
241      document.body.dataset.membershipPreview = "true";
242      if (membershipLabel) {
243        membershipLabel.textContent =
244          "Enrollment status is temporarily unavailable";
245      }
246      if (checkoutButton) {
247        checkoutButton.disabled = true;
248        checkoutButton.setAttribute("aria-disabled", "true");
249      }
250      if (checkoutLabel) checkoutLabel.textContent = "Enrollment unavailable";
251      showCheckoutError(
252        "We could not confirm membership availability. Please try again shortly or contact [email protected].",
253      );
254    }
255  }
256
257  checkoutButton?.addEventListener("click", () => {
258    clearCheckoutError();
259
260    if (!checkoutReady) {
261      showDialog();
262      return;
263    }
264
265    setCheckoutLoading(true);
266    const query = new URLSearchParams({ plan: selectedBillingPlan });
267    window.location.assign(`/barons-club/join/?${query.toString()}`);
268  });
269
270  waitlistForm?.addEventListener("submit", async (event) => {
271    event.preventDefault();
272    waitlistMessage.hidden = true;
273    waitlistMessage.classList.remove("is-error");
274
275    if (!waitlistForm.reportValidity()) return;
276
277    const submitButton = waitlistForm.querySelector('button[type="submit"]');
278    const formData = new FormData(waitlistForm);
279    const turnstileToken =
280      turnstileWidgetId !== null && window.turnstile
281        ? window.turnstile.getResponse(turnstileWidgetId)
282        : "";
283
284    const payload = {
285      firstName: formData.get("firstName")?.toString().trim(),
286      lastName: formData.get("lastName")?.toString().trim(),
287      email: formData.get("email")?.toString().trim(),
288      phone: formData.get("phone")?.toString().trim(),
289      companyTitle: formData.get("companyTitle")?.toString().trim(),
290      website: formData.get("website")?.toString(),
291      consent: formData.get("consent") === "yes",
292      turnstileToken,
293    };
294
295    submitButton.disabled = true;
296    submitButton.setAttribute("aria-busy", "true");
297    if (waitlistLabel) waitlistLabel.textContent = "Saving your place";
298
299    try {
300      const result = await requestJson("/api/membership/waitlist", {
301        method: "POST",
302        body: JSON.stringify(payload),
303      });
304
305      if (!result.response.ok) {
306        throw new Error(
307          result.payload.message ||
308            "Your waitlist request could not be saved. Please try again.",
309        );
310      }
311
312      waitlistForm.reset();
313      waitlistMessage.textContent =
314        result.payload.message ||
315        "You are on the waitlist. Watch your inbox for Club updates.";
316      waitlistMessage.hidden = false;
317      if (turnstileWidgetId !== null && window.turnstile) {
318        window.turnstile.reset(turnstileWidgetId);
319      }
320    } catch (error) {
321      waitlistMessage.textContent =
322        error.name === "AbortError"
323          ? "The request took too long. Please try again."
324          : error.message ||
325            "Your waitlist request could not be saved. Please try again.";
326      waitlistMessage.classList.add("is-error");
327      waitlistMessage.hidden = false;
328    } finally {
329      submitButton.disabled = false;
330      submitButton.removeAttribute("aria-busy");
331      if (waitlistLabel) waitlistLabel.textContent = "Join the Waitlist";
332    }
333  });
334
335  async function loadWelcomeSession() {
336    const welcomeMessage = document.querySelector("[data-welcome-message]");
337    if (!welcomeMessage) return;
338
339    const memberName = document.querySelector("[data-member-name]");
340    const welcomeKicker = document.querySelector("[data-welcome-kicker]");
341    const sessionId = new URLSearchParams(window.location.search).get(
342      "session_id",
343    );
344
345    if (!sessionId) {
346      if (welcomeKicker) welcomeKicker.textContent = "The Baron’s Club";
347      return;
348    }
349
350    if (isStaticPreview) return;
351
352    try {
353      const { response, payload } = await requestJson(
354        `/api/membership/session?session_id=${encodeURIComponent(sessionId)}`,
355        { method: "GET", headers: {} },
356      );
357      if (!response.ok || !payload.confirmed) throw new Error("Not confirmed");
358
359      if (payload.firstName && memberName) {
360        memberName.textContent = `Welcome, ${payload.firstName}.`;
361        memberName.hidden = false;
362      }
363      setPortalUrl(payload.portalUrl);
364    } catch {
365      if (welcomeKicker) welcomeKicker.textContent = "Confirmation received";
366    }
367  }
368
369  document.querySelectorAll(".club-faq details").forEach((detail) => {
370    detail.addEventListener("toggle", () => {
371      if (!detail.open) return;
372      document.querySelectorAll(".club-faq details[open]").forEach((item) => {
373        if (item !== detail) item.open = false;
374      });
375    });
376  });
377
378  if (
379    mobileActionBar &&
380    membershipOffer &&
381    "IntersectionObserver" in window
382  ) {
383    const actionBarObserver = new IntersectionObserver(
384      ([entry]) => {
385        mobileActionBar.classList.toggle(
386          "is-context-hidden",
387          entry.isIntersecting,
388        );
389      },
390      {
391        rootMargin: "-6% 0px -12% 0px",
392        threshold: 0,
393      },
394    );
395    actionBarObserver.observe(membershipOffer);
396  }
397
398  loadMembershipStatus();
399  loadWelcomeSession();
400  setBillingPlan(
401    billingInputs.find((input) => input.checked)?.value || "annual",
402  );
403})();

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.