diff --git a/site/donate/donate.js b/site/donate/donate.js index 1ed8010..4855af2 100644 --- a/site/donate/donate.js +++ b/site/donate/donate.js @@ -2,29 +2,36 @@ // // Handles the interactive bits of the donation form: // - Frequency + payment method picker (Monthly · DD / One-off · Card) -// - Suggested-amount selection -// - "Other" -> custom amount input +// - Suggested-amount selection (presets only for Monthly; "Other" shown for One-off) +// - "Other" -> custom amount input (One-off only — Stripe sets the amount there) // - Dynamic CTA label ("Donate £5 a month" / "Donate £5") -// - On submit: POST to the Cloudflare Worker that creates a Stripe -// Checkout Session and redirects the donor there. -// -// The Stripe call itself is STUBBED below until the Worker is deployed -// and the Stripe account exists. Search for TODO(stripe). +// - On submit: redirect to the matching Stripe Payment Link (function () { 'use strict'; // ── Config ───────────────────────────────────────────── - // TODO(stripe): replace with the deployed Cloudflare Worker URL once we - // have it (e.g. https://donate-api.pauseai-uk.workers.dev/checkout). - const CHECKOUT_ENDPOINT = '/api/create-checkout-session'; + // Stripe Payment Links. The one-off link is "customer chooses price" — + // the donor confirms the amount on Stripe's secure page. The monthly + // links are fixed-amount recurring, so each preset routes to its own. + const ONE_OFF_URL = 'https://donate.stripe.com/14AaEY7J69wKgTAaSlcbC02'; + const MONTHLY_URLS = { + 3: 'https://donate.stripe.com/3cI3cwgfCgZc46O4tXcbC03', + 5: 'https://donate.stripe.com/3cIdRa7J610ebzg4tXcbC00', + 10: 'https://donate.stripe.com/cNi3cwd3q24i5aS1hLcbC01', + 25: 'https://donate.stripe.com/14AcN61kI7oCbzgaSlcbC04', + 50: 'https://donate.stripe.com/7sYeVebZmcIW8n41hLcbC05', + 100: 'https://donate.stripe.com/9B6bJ2d3qeR446O9OhcbC06', + 250: 'https://donate.stripe.com/dRmfZi1kIfV8bzg2lPcbC07', + 500: 'https://donate.stripe.com/cNi28s5AY5gucDkbWpcbC08', + }; const MIN_AMOUNT = 3; // pounds // ── State ────────────────────────────────────────────── const state = { - frequency: 'monthly', // 'monthly' | 'oneoff' + frequency: 'monthly', // 'monthly' | 'oneoff' paymentMethod: 'bacs_debit', // 'bacs_debit' | 'card' - amount: 5, // pounds; null when "other" is picked with no value + amount: 5, // pounds; null when "other" is picked with no value isCustom: false, }; @@ -34,24 +41,47 @@ const freqOptions = form.querySelectorAll('.freq-option'); const amountOptions = form.querySelectorAll('.amount-option'); + const amountSelector = form.querySelector('.amount-selector'); + const otherBtn = form.querySelector('.amount-other'); const customWrap = form.querySelector('.amount-custom'); const customInput = form.querySelector('#custom-amount'); const submitBtn = form.querySelector('#donate-submit'); const ctaLabel = form.querySelector('#donate-cta-label'); const customSuffix = form.querySelector('.amount-input-wrap [data-freq-suffix]'); - // ── UI updates ───────────────────────────────────────── + // ── Helpers ──────────────────────────────────────────── function updateCtaLabel() { - const amountText = state.amount && state.amount >= MIN_AMOUNT - ? '£' + state.amount - : '£…'; - const tail = state.frequency === 'monthly' ? ' a month' : ''; - if (ctaLabel) ctaLabel.textContent = 'Donate ' + amountText + tail; + if (!ctaLabel) return; + if (state.frequency === 'oneoff') { + ctaLabel.textContent = 'Donate one-off →'; + } else { + const amountText = state.amount && state.amount >= MIN_AMOUNT + ? '£' + state.amount + : '£…'; + ctaLabel.textContent = 'Donate ' + amountText + ' a month'; + } if (customSuffix) { customSuffix.textContent = state.frequency === 'monthly' ? 'per month' : 'one-off'; } } + function setAmountSelectorVisible(visible) { + if (amountSelector) amountSelector.hidden = !visible; + } + + function setOtherVisible(visible) { + if (otherBtn) otherBtn.hidden = !visible; + } + + function activatePreset(amount) { + const btn = form.querySelector('.amount-option[data-amount="' + amount + '"]'); + if (!btn) return; + amountOptions.forEach((b) => b.classList.toggle('is-active', b === btn)); + state.amount = amount; + state.isCustom = false; + customWrap.hidden = true; + } + // ── Frequency / method tabs ──────────────────────────── freqOptions.forEach((btn) => { btn.addEventListener('click', () => { @@ -65,6 +95,16 @@ b.classList.toggle('is-active', active); b.setAttribute('aria-checked', active ? 'true' : 'false'); }); + // Monthly: show preset grid (no custom recurring), hide Other. + // One-off: hide the grid entirely — the donor picks the amount on + // Stripe's "customer chooses price" page. + if (freq === 'monthly') { + setAmountSelectorVisible(true); + setOtherVisible(false); + if (state.isCustom) activatePreset(5); + } else { + setAmountSelectorVisible(false); + } updateCtaLabel(); }); }); @@ -94,15 +134,22 @@ updateCtaLabel(); }); - // ── Submit ───────────────────────────────────────────── + // ── Submit → redirect to the matching Stripe Payment Link ─ function validate() { + // One-off: Stripe's "customer chooses price" page enforces the amount. + if (state.frequency === 'oneoff') return null; if (!Number.isFinite(state.amount) || state.amount < MIN_AMOUNT) { return `Please enter an amount of £${MIN_AMOUNT} or more.`; } return null; } - form.addEventListener('submit', async (event) => { + function targetUrl() { + if (state.frequency === 'oneoff') return ONE_OFF_URL; + return MONTHLY_URLS[state.amount] || null; + } + + form.addEventListener('submit', (event) => { event.preventDefault(); const err = validate(); if (err) { @@ -110,50 +157,18 @@ if (state.isCustom) customInput.focus(); return; } - + const url = targetUrl(); + if (!url) { + alert('Sorry — that amount is not available right now. Please pick a preset or email hello@pauseai.uk.'); + return; + } submitBtn.disabled = true; - const previousLabel = submitBtn.innerHTML; submitBtn.textContent = 'Redirecting…'; - - try { - // TODO(stripe): once the Cloudflare Worker is live, this fetch will - // create a Checkout Session server-side and return { url } that we - // redirect the donor to. Until then we just log what would be sent. - const payload = { - amount_pounds: state.amount, - frequency: state.frequency, // 'monthly' | 'oneoff' - payment_method: state.paymentMethod, // 'bacs_debit' | 'card' - }; - console.log('[donate] would POST to', CHECKOUT_ENDPOINT, payload); - alert( - 'Stripe is not wired up yet. This would create a ' + - state.frequency + - ' £' + - state.amount + - ' donation via ' + - (state.paymentMethod === 'bacs_debit' ? 'Direct Debit' : 'card / wallet') + - '.' - ); - - // Real implementation (commented until Worker is deployed): - // - // const res = await fetch(CHECKOUT_ENDPOINT, { - // method: 'POST', - // headers: { 'Content-Type': 'application/json' }, - // body: JSON.stringify(payload), - // }); - // if (!res.ok) throw new Error('Checkout session failed'); - // const { url } = await res.json(); - // window.location.href = url; - } catch (e) { - console.error(e); - alert('Something went wrong. Please try again or email joseph@pauseai.info.'); - } finally { - submitBtn.disabled = false; - submitBtn.innerHTML = previousLabel; - } + window.location.href = url; }); - // Initial render + // Initial render — default is Monthly: show preset grid, hide Other. + setAmountSelectorVisible(true); + setOtherVisible(false); updateCtaLabel(); })(); diff --git a/site/donate/index.html b/site/donate/index.html index 0502aee..e545506 100644 --- a/site/donate/index.html +++ b/site/donate/index.html @@ -166,6 +166,8 @@

Let's press pause on unsafe AI together

+ +