Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 76 additions & 61 deletions site/donate/donate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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', () => {
Expand All @@ -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();
});
});
Expand Down Expand Up @@ -94,66 +134,41 @@
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) {
alert(err);
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();
})();
25 changes: 10 additions & 15 deletions site/donate/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ <h1>Let's press pause on unsafe AI together</h1>
<button type="button" class="amount-option" data-amount="25">£25</button>
<button type="button" class="amount-option" data-amount="50">£50</button>
<button type="button" class="amount-option" data-amount="100">£100</button>
<button type="button" class="amount-option" data-amount="250">£250</button>
<button type="button" class="amount-option" data-amount="500">£500</button>
<button type="button" class="amount-option amount-other" data-amount="other">Other</button>
</div>
<div class="amount-custom" hidden>
Expand Down Expand Up @@ -219,37 +221,30 @@ <h1>Let's press pause on unsafe AI together</h1>
<div class="donate-bottom-grid">

<div id="other-ways" class="donate-bottom-col">
<p class="eyebrow">Other ways to give</p>
<h2>Major gifts &amp; bank transfer</h2>
<p class="section-lede">
If you're considering a larger gift, prefer to give by bank
transfer, or represent a foundation, please get in touch.
Prefer to give by bank transfer, making a large one-off gift, or
giving on behalf of a foundation? Get in touch and we'll help
directly — and say a proper thank-you.
</p>
<div class="other-ways-cards">
<article class="feature-card">
<h3>Bank transfer &amp; major donors</h3>
<h3>Bank transfer &amp; major gifts</h3>
<p>
For UK bank transfers (Faster Payments / BACS) and gifts
above £500, please email us so we can share account
details directly.
For UK bank transfers (Faster Payments / BACS) or larger
one-off donations, email us and we'll share account details
directly. (Regular gifts of any size are easy above — Direct
Debit keeps our fees low.)
</p>
<a
class="inline-link"
href="mailto:hello@pauseai.uk?subject=PauseAI%20UK%20donation%20enquiry"
>hello@pauseai.uk →</a>
</article>
<article class="feature-card">
<h3>US-based donors</h3>
<p>
We're working on enabling tax-deductible giving for US
supporters. <em>Coming soon.</em>
</p>
</article>
</div>
</div>

<div id="transparency" class="donate-bottom-col">
<p class="eyebrow">Trust &amp; transparency</p>
<h2>Where your money goes</h2>
<p class="section-lede">
Your gift funds protests, organising, outreach, and our small
Expand Down
6 changes: 4 additions & 2 deletions site/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1311,7 +1311,7 @@ img.news-logo[alt="Fortune"] {
.donate-intro-head { grid-area: head; }
.donate-intro-text { grid-area: text; }
.donate-intro-photo { grid-area: photo; }
.donate-intro-form { grid-area: form; align-self: stretch; display: flex; }
.donate-intro-form { grid-area: form; align-self: start; display: flex; }

.donate-intro h1 {
font-size: clamp(38px, 4.6vw, 56px);
Expand All @@ -1320,6 +1320,9 @@ img.news-logo[alt="Fortune"] {
margin: 6px 0 0;
color: var(--text);
}
@media (min-width: 721px) {
.donate-intro h1 { margin-bottom: 24px; }
}
.donate-intro .lede {
font-size: 17px;
line-height: 1.6;
Expand Down Expand Up @@ -1437,7 +1440,6 @@ img.news-logo[alt="Fortune"] {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}

/* Frequency + payment method picker */
Expand Down
Loading