donate.js

/* global Stripe */
const publishableKey = document.querySelector('meta[name="stripe-publishable-key"]')?.content || '';
const stripe         = publishableKey ? Stripe(publishableKey) : null;

let elements      = null;
let selectedCents = 500; // default $5

const presetBtns   = document.querySelectorAll('.amount-preset-btn');
const customInput  = document.getElementById('customAmount');
const submitBtn    = document.getElementById('donateSubmitBtn');
const errorEl      = document.getElementById('donateError');
const formEl       = document.getElementById('donateForm');
const successEl    = document.getElementById('donateSuccess');

/** @param {string} msg - Error message to display, or empty string to clear. */
function setError(msg) {
    if (errorEl) errorEl.textContent = msg || '';
}

/** Updates donate button label to reflect current amount. */
function updateSubmitLabel() {
    const dollars = (selectedCents / 100).toFixed(2);
    if (submitBtn) submitBtn.textContent = `Donate $${dollars}`;
}

/** @param {number} cents - Donation amount in cents. */
function selectPreset(cents) {
    selectedCents = cents;
    presetBtns.forEach(b => b.classList.toggle('active', parseInt(b.dataset.amount, 10) === cents));
    if (customInput) customInput.value = '';
    updateSubmitLabel();
    reinitElements();
}

/** @param {string} dollars - Dollar amount entered by user. */
function selectCustom(dollars) {
    const cents = Math.round(parseFloat(dollars) * 100);
    if (isNaN(cents) || cents < 100) return;
    selectedCents = cents;
    presetBtns.forEach(b => b.classList.remove('active'));
    updateSubmitLabel();
    reinitElements();
}

/** Fetches new PaymentIntent and mounts Stripe payment element. */
async function reinitElements() {
    if (!stripe) return;
    setError('');
    if (submitBtn) submitBtn.disabled = true;

    // Create a new PaymentIntent for the selected amount
    let clientSecret;
    try {
        const res  = await fetch('/api/stripe/create-donation-intent', {
            method:  'POST',
            headers: { 'Content-Type': 'application/json' },
            body:    JSON.stringify({ amount_cents: selectedCents }),
        });
        const data = await res.json();
        if (!res.ok) { setError(data.error || 'Failed to initialise payment.'); return; }
        clientSecret = data.client_secret;
    } catch {
        setError('Network error — please try again.');
        return;
    }

    const paymentEl = document.getElementById('payment-element');
    if (paymentEl) paymentEl.innerHTML = '';

    elements = stripe.elements({ clientSecret });
    const paymentElement = elements.create('payment');
    paymentElement.mount('#payment-element');
    paymentElement.on('ready', () => { if (submitBtn) submitBtn.disabled = false; });
}

// ── Wire preset buttons ──────────────────────────────────────────────────────
presetBtns.forEach(btn => {
    btn.addEventListener('click', () => selectPreset(parseInt(btn.dataset.amount, 10)));
});

// ── Wire custom input ────────────────────────────────────────────────────────
let customDebounce;
customInput?.addEventListener('input', (e) => {
    clearTimeout(customDebounce);
    customDebounce = setTimeout(() => selectCustom(e.target.value), 600);
});

// ── Wire submit ──────────────────────────────────────────────────────────────
submitBtn?.addEventListener('click', async () => {
    if (!stripe || !elements) return;
    setError('');
    submitBtn.disabled = true;
    submitBtn.textContent = 'Processing…';

    const { error } = await stripe.confirmPayment({
        elements,
        redirect: 'if_required',
    });

    if (error) {
        setError(error.message || 'Payment failed. Please try again.');
        submitBtn.disabled = false;
        updateSubmitLabel();
    } else {
        if (formEl)    formEl.style.display    = 'none';
        if (successEl) successEl.style.display = '';
    }
});

// ── Initialise on load ───────────────────────────────────────────────────────
if (stripe) {
    reinitElements();
} else {
    setError('Stripe is not configured. Donations are not available yet.');
}