Add El source files for all client-side JS
Recovers original JS from git history and ports it into proper El source files under src/js/. Each file wraps the original JS in a native_js call inside a main() function, making it valid El that compiles to a self-contained IIFE via elc --target=js --bundle. Files added: src/js/account-auth.el - Supabase OTP magic-link (sendMagicLink) src/js/account-dashboard.el - Account dashboard: session, plan card, family src/js/chat-widget.el - Demo chat widget (neuronDemoToggle/Send/Reset) src/js/checkout-auth.el - Checkout auth: OAuth, email sign-in/up src/js/checkout-free.el - Free plan: auth-badge watch -> payment reveal src/js/checkout-stripe.el - Stripe Payment Element (reads NEURON_CFG) src/js/enterprise.el - Enterprise inquiry form + headcount filter src/js/environmental.el - Efficiency calculator slider src/js/gallery.el - Gallery nav, search/sort, Supabase voting src/js/main.el - Share page voting + copyForPlatform src/js/marketplace.el - Developer interest form src/js/nav.el - Nav hamburger + Mission dropdown src/js/styles.el - Landing: nav scroll, reveal, founding counter
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
// checkout-stripe.el -- Stripe Payment Element setup and form submission.
|
||||
// Reads NEURON_CFG.plan and NEURON_CFG.pub_key from window.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Required globals: window.NEURON_CFG.plan, window.NEURON_CFG.pub_key
|
||||
// Required CDN: Stripe.js loaded before this script
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var cfg = window.NEURON_CFG || {};
|
||||
var PLAN = cfg.plan || '';
|
||||
var STRIPE_PK = cfg.pub_key || '';
|
||||
var stripe, elements;
|
||||
|
||||
function waitForStripe(cb) {
|
||||
if (window.Stripe) { cb(); return; }
|
||||
setTimeout(function() { waitForStripe(cb); }, 50);
|
||||
}
|
||||
|
||||
function showMessage(msg) {
|
||||
var el = document.getElementById('payment-message');
|
||||
if (el) { el.textContent = msg; el.style.display = 'block'; }
|
||||
}
|
||||
|
||||
function setLoading(loading) {
|
||||
var btn = document.getElementById('submit-btn');
|
||||
var label = document.getElementById('submit-label');
|
||||
var spinner = document.getElementById('submit-spinner');
|
||||
if (btn) btn.disabled = loading;
|
||||
if (label) label.style.display = loading ? 'none' : '';
|
||||
if (spinner) spinner.style.display = loading ? '' : 'none';
|
||||
}
|
||||
|
||||
window._neuronMode = 'payment';
|
||||
var paymentEl = null;
|
||||
|
||||
function appearance() {
|
||||
return {
|
||||
theme: 'flat',
|
||||
variables: {
|
||||
colorPrimary: '#0052A0',
|
||||
colorBackground: '#ffffff',
|
||||
colorText: '#1A1A2E',
|
||||
colorDanger: '#c0392b',
|
||||
colorTextPlaceholder: '#9B9BAD',
|
||||
borderRadius: '0px',
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif',
|
||||
fontSizeBase: '15px',
|
||||
fontWeightNormal: '300',
|
||||
spacingUnit: '4px'
|
||||
},
|
||||
rules: {
|
||||
'.Input': { border: '1px solid rgba(0,82,160,.22)', boxShadow: 'none', padding: '10px 14px' },
|
||||
'.Input:focus': { border: '1px solid rgba(0,82,160,.6)', boxShadow: '0 0 0 3px rgba(0,82,160,.08)', outline: 'none' },
|
||||
'.Label': { fontSize: '11px', fontWeight: '500', letterSpacing: '.06em', textTransform: 'uppercase', color: '#6B6B7E', marginBottom: '6px' },
|
||||
'.Tab': { border: '1px solid rgba(0,82,160,.18)', boxShadow: 'none' },
|
||||
'.Tab--selected':{ border: '1px solid rgba(0,82,160,.5)', boxShadow: '0 0 0 2px rgba(0,82,160,.12)' },
|
||||
'.Error': { color: '#c0392b' }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function currentTiming() {
|
||||
var later = document.getElementById('timing-later');
|
||||
return (later && later.checked) ? 'later' : 'now';
|
||||
}
|
||||
|
||||
function fetchAndMount() {
|
||||
var submitBtn = document.getElementById('submit-btn');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
if (paymentEl) { try { paymentEl.unmount(); } catch(e) {} paymentEl = null; }
|
||||
var hostEl = document.getElementById('payment-element');
|
||||
if (hostEl && !document.querySelector('.checkout-element-loading')) {
|
||||
var d = document.createElement('div');
|
||||
d.className = 'checkout-element-loading';
|
||||
d.textContent = 'Loading payment form...';
|
||||
hostEl.appendChild(d);
|
||||
}
|
||||
var timing = currentTiming();
|
||||
return fetch('/api/payment-intent', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ plan: PLAN, timing: timing })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.error === 'sold_out') {
|
||||
showMessage('All 1,000 Founding Member spots have been claimed. Thank you for your interest - please consider the Professional plan.');
|
||||
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Sold out'; }
|
||||
return;
|
||||
}
|
||||
if (!data.client_secret) { showMessage('Unable to initialise payment. Please try again.'); return; }
|
||||
window._neuronMode = data.setup_mode ? 'setup' : 'payment';
|
||||
window._neuronPiId = data.id || (data.client_secret ? data.client_secret.split('_secret_')[0] : '');
|
||||
var submitLabel = document.getElementById('submit-label');
|
||||
if (submitLabel) {
|
||||
submitLabel.textContent = window._neuronMode === 'setup'
|
||||
? 'Save my card - no charge today →'
|
||||
: 'Complete purchase →';
|
||||
}
|
||||
waitForStripe(function() {
|
||||
if (!stripe) stripe = Stripe(STRIPE_PK);
|
||||
elements = stripe.elements({ clientSecret: data.client_secret, appearance: appearance() });
|
||||
paymentEl = elements.create('payment', {
|
||||
fields: { billingDetails: { name: 'never', email: 'never' } }
|
||||
});
|
||||
paymentEl.mount('#payment-element');
|
||||
paymentEl.on('ready', function() {
|
||||
var ld = document.querySelector('.checkout-element-loading');
|
||||
if (ld) ld.remove();
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function() {
|
||||
showMessage('Unable to connect. Please check your connection and try again.');
|
||||
});
|
||||
}
|
||||
|
||||
fetchAndMount();
|
||||
var tNow = document.getElementById('timing-now');
|
||||
var tLater = document.getElementById('timing-later');
|
||||
if (tNow) tNow.addEventListener('change', fetchAndMount);
|
||||
if (tLater) tLater.addEventListener('change', fetchAndMount);
|
||||
|
||||
var form = document.getElementById('payment-form');
|
||||
if (form) form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
if (!stripe || !elements) return;
|
||||
|
||||
var attestCb = document.getElementById('founding-attest-cb');
|
||||
if (attestCb && !attestCb.checked) {
|
||||
var warn = document.getElementById('attest-warn');
|
||||
if (warn) warn.style.display = 'block';
|
||||
attestCb.closest('label').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
|
||||
var name = document.getElementById('buyer-name').value.trim();
|
||||
var email = document.getElementById('buyer-email').value.trim();
|
||||
if (!name || !email) { showMessage('Please enter your name and email.'); return; }
|
||||
|
||||
if (attestCb) {
|
||||
try {
|
||||
await fetch('/api/attest', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
plan: PLAN, name: name, email: email,
|
||||
timestamp: new Date().toISOString(),
|
||||
attestation: 'I am joining as a genuine early user, not to extract proprietary information about Neuron technology, architecture, or roadmap. I will engage in good faith. I understand that if this is not my intent, a different plan is a better fit.',
|
||||
user_agent: navigator.userAgent
|
||||
})
|
||||
});
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
var pmsg = document.getElementById('payment-message');
|
||||
if (pmsg) pmsg.style.display = 'none';
|
||||
|
||||
if (window._neuronPiId) {
|
||||
try {
|
||||
await fetch('/api/link-customer', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
pi_id: window._neuronPiId,
|
||||
email: email,
|
||||
name: name,
|
||||
plan: PLAN,
|
||||
timing: currentTiming(),
|
||||
mode: window._neuronMode || 'payment',
|
||||
supabase_user_id: window._neuronSupaId || ''
|
||||
})
|
||||
});
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
var confirmParams = {
|
||||
return_url: window.location.origin + '/account?welcome=1',
|
||||
payment_method_data: { billing_details: { name: name, email: email } }
|
||||
};
|
||||
var result;
|
||||
if (window._neuronMode === 'setup') {
|
||||
result = await stripe.confirmSetup({ elements: elements, confirmParams: confirmParams });
|
||||
} else {
|
||||
confirmParams.receipt_email = email;
|
||||
result = await stripe.confirmPayment({ elements: elements, confirmParams: confirmParams });
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
showMessage(result.error.message || (window._neuronMode === 'setup'
|
||||
? 'Could not save your card. Please try again.'
|
||||
: 'Payment failed. Please try again.'));
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
})()")
|
||||
}
|
||||
Reference in New Issue
Block a user