// account-dashboard.el -- Account dashboard: session check, plan card, family. // Handles onAuthStateChange, plan card rendering, family member management. // Compiled with: elc --target=js --bundle --minify --obfuscate // // Required globals: window.NEURON_CFG.supabase_url, window.NEURON_CFG.supabase_anon_key // Required CDN: supabase-js@2 fn main() -> Void { native_js("(function() { 'use strict'; var cfg = window.NEURON_CFG || {}; var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, { auth: { flowType: 'implicit' } }); function show(id) { var el = document.getElementById(id); if (el) el.style.display = ''; } function hide(id) { var el = document.getElementById(id); if (el) el.style.display = 'none'; } function setText(id, text) { var el = document.getElementById(id); if (el) el.textContent = text; } function setHtml(id, html) { var el = document.getElementById(id); if (el) el.innerHTML = html; } window.signInWith = async function(provider) { var btn = document.getElementById('btn-' + provider); if (btn) { btn.disabled = true; btn.style.opacity = '0.6'; } try { var result = await sb.auth.signInWithOAuth({ provider: provider, options: { redirectTo: window.location.origin + '/account' } }); if (result.error) { if (btn) { btn.disabled = false; btn.style.opacity = '1'; } } } catch (e) { if (btn) { btn.disabled = false; btn.style.opacity = '1'; } } }; window.signInWithEmail = async function() { var email = document.getElementById('acct-email-input').value.trim(); var pass = document.getElementById('acct-pass-input').value; var msg = document.getElementById('acct-email-msg'); var signinBtn = document.getElementById('acct-signin-btn'); if (!sb) { msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Loading... try again in a moment.'; return; } if (!email || !pass) { msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Please enter your email and password.'; return; } if (signinBtn) { signinBtn.disabled=true; signinBtn.textContent='Signing in...'; } var result = await sb.auth.signInWithPassword({ email: email, password: pass }); if (result.error) { if (result.error.message && result.error.message.toLowerCase().includes('invalid')) { var signupResult = await sb.auth.signUp({ email: email, password: pass, options: { emailRedirectTo: window.location.origin + '/account' } }); if (signupResult.error) { msg.style.display='block'; msg.style.color='#c44'; msg.textContent=signupResult.error.message; return; } msg.style.display='block'; msg.style.color='var(--navy)'; msg.textContent='Check your email to confirm your account.'; return; } if (signinBtn) { signinBtn.disabled=false; signinBtn.textContent='Sign in'; } msg.style.display='block'; msg.style.color='#c44'; msg.textContent=result.error.message; return; } window.location.reload(); }; window.signOut = async function() { var btn = document.getElementById('signout-btn'); var btnTop = document.getElementById('signout-btn-top'); if (btn) { btn.disabled=true; btn.textContent='Signing out...'; } if (btnTop) { btnTop.disabled=true; btnTop.textContent='Signing out...'; } await sb.auth.signOut(); show('signin-section'); hide('dashboard-section'); if (btn) { btn.disabled=false; btn.textContent='Sign out'; } if (btnTop) { btnTop.disabled=false; btnTop.textContent='Sign out'; } }; async function renderPlanCard(row) { var plan = (row && row.plan) ? row.plan : 'free'; var memberNum = (row && row.member_number) ? row.member_number : null; var createdAt = (row && row.created_at) ? row.created_at : null; var planNames = { 'founding': 'Founding Member', 'professional': 'Professional', 'free': 'Free' }; setText('plan-name-el', planNames[plan] || 'Free'); var statusHtml = ''; if (plan === 'founding' || plan === 'professional') { statusHtml = '' + '' + 'Launching within 30 days'; } else { statusHtml = 'Active'; } setHtml('plan-status-el', statusHtml); var billingNote = ''; if (plan === 'founding') { billingNote = '

Lifetime · Never billed again

'; } else if (plan === 'professional') { billingNote = '

Billed monthly ·

'; } else { billingNote = '

On the waitlist

'; } setHtml('plan-billing-note-el', billingNote); var meta = ''; if (createdAt) { var d = new Date(createdAt); var dateStr = d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); meta += '
Joined' + dateStr + '
'; } if (memberNum) { meta += '
Member number#' + memberNum + ' of 1,000
'; } if (meta) setHtml('plan-meta-el', meta); if (plan === 'founding') { var badgeSection = document.getElementById('badge-section'); var badgeContainer = document.getElementById('badge-html-container'); if (badgeSection) badgeSection.style.display = ''; var badgeN = memberNum || 0; fetch('/api/founding-badge?n=' + badgeN) .then(function(r) { return r.text(); }) .then(function(html) { if (badgeContainer) badgeContainer.innerHTML = html; }) .catch(function() {}); } var roadmapSection = document.getElementById('roadmap-section'); if (plan === 'founding' && roadmapSection) roadmapSection.style.display = ''; if (plan === 'founding') { var famSection = document.getElementById('family-section'); if (famSection) famSection.style.display = 'block'; var session = await sb.auth.getSession(); var userEmail = session.data.session && session.data.session.user ? session.data.session.user.email : ''; if (userEmail) loadFamilyMembers(userEmail); } } async function loadFamilyMembers(parentEmail) { var r = await fetch('/api/family/members?parent_email=' + encodeURIComponent(parentEmail)); var members = await r.json(); var list = document.getElementById('family-list'); if (!list) return; if (!members || !members.length) { list.innerHTML = '

No family members yet.

'; return; } list.innerHTML = members.map(function(m) { return '
' + '

' + m.child_email + '

' + '

' + m.status + '

' + '' + '
'; }).join(''); } window.addFamilyMember = async function() { var email = document.getElementById('child-email').value.trim(); var year = document.getElementById('child-dob-year').value; var attest = document.getElementById('family-attest').checked; var msg = document.getElementById('family-msg'); if (!email || !year || !attest) { msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Please fill in all fields and confirm the attestation.'; return; } if (parseInt(year) < 2008) { msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Child must be under 18. Birth year must be 2008 or later.'; return; } var session = await sb.auth.getSession(); var parentEmail = session.data.session && session.data.session.user ? session.data.session.user.email : ''; var r = await fetch('/api/family/invite', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({parent_email: parentEmail, child_email: email, child_dob_year: parseInt(year), attested: true}) }); var d = await r.json(); msg.style.display = 'block'; if (d.ok) { msg.style.color = 'var(--navy)'; msg.textContent = 'Invitation sent to ' + email + '. They will receive an email to set up their account.'; document.getElementById('child-email').value = ''; document.getElementById('child-dob-year').value = ''; document.getElementById('family-attest').checked = false; loadFamilyMembers(parentEmail); } else { msg.style.color = '#c44'; msg.textContent = d.error || 'Something went wrong.'; } }; window.removeFamilyMember = async function(childEmail) { var session = await sb.auth.getSession(); var parentEmail = session.data.session && session.data.session.user ? session.data.session.user.email : ''; await fetch('/api/family/remove', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({parent_email: parentEmail, child_email: childEmail}) }); loadFamilyMembers(parentEmail); }; function renderUserChip(user) { var email = user.email || ''; var avatarEl = document.getElementById('user-avatar-el'); var emailEl = document.getElementById('user-email-el'); var headerEmailEl = document.getElementById('acct-header-email'); if (emailEl) emailEl.textContent = email; if (headerEmailEl) headerEmailEl.textContent = email; var avatarUrl = user.user_metadata && user.user_metadata.avatar_url; if (avatarEl) { if (avatarUrl) { avatarEl.innerHTML = '\"\"'; } else { avatarEl.textContent = email ? email.charAt(0).toUpperCase() : '?'; } } } function showNoPlan() { var el = document.getElementById('plan-card'); if (!el) return; el.innerHTML = '
Your plan
' + '

No active plan

' + '

You have an account but no plan selected yet. Pick one below to preorder.

' + '
' + 'Founding Member - $199 →' + 'Professional - $19/mo' + 'Free tier' + '
'; } async function loadWaitlistData() { try { var sess = await sb.auth.getSession(); var token = sess.data && sess.data.session ? sess.data.session.access_token : ''; if (!token) { showNoPlan(); return; } var r = await fetch('/api/my-plan', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({access_token: token}) }); var row = await r.json(); if (!row || !row.plan) { showNoPlan(); return; } renderPlanCard(row); } catch (e) { showNoPlan(); } } async function loadApiKeys() { var apiSection = document.getElementById('api-keys-section'); if (apiSection) apiSection.style.display = ''; try { var sess = await sb.auth.getSession(); var token = sess.data && sess.data.session ? sess.data.session.access_token : ''; if (!token) return; var r = await fetch('/api/api-keys', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({access_token: token}) }); var d = await r.json(); if (!d.rows || !d.rows.length) return; d.rows.forEach(function(row) { var provider = row.provider; var keyVal = row.key_value || ''; var maskedEl = document.getElementById('apikey-masked-' + provider); var delBtn = document.getElementById('apikey-del-' + provider); if (keyVal) { var klen = keyVal.length; var masked = klen <= 10 ? '••••••••' : keyVal.slice(0, 6) + '••••' + keyVal.slice(-4); if (maskedEl) maskedEl.textContent = masked; if (delBtn) delBtn.style.display = ''; } }); } catch (e) {} } window.saveApiKey = async function(provider) { var input = document.getElementById('apikey-input-' + provider); var msg = document.getElementById('api-keys-msg'); if (!input || !input.value.trim()) { if (msg) { msg.style.display = 'block'; msg.style.color = '#c44'; msg.textContent = 'Enter a key first.'; } return; } var keyVal = input.value.trim(); var sess = await sb.auth.getSession(); var token = sess.data && sess.data.session ? sess.data.session.access_token : ''; if (!token) return; try { var r = await fetch('/api/api-keys/save', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({access_token: token, provider: provider, key: keyVal}) }); var d = await r.json(); if (d.ok) { var maskedEl = document.getElementById('apikey-masked-' + provider); var delBtn = document.getElementById('apikey-del-' + provider); if (maskedEl) maskedEl.textContent = d.masked; if (delBtn) delBtn.style.display = ''; input.value = ''; if (msg) { msg.style.display = 'block'; msg.style.color = 'var(--navy)'; msg.textContent = provider.charAt(0).toUpperCase() + provider.slice(1) + ' key saved.'; } setTimeout(function() { if (msg) msg.style.display = 'none'; }, 3000); } else { if (msg) { msg.style.display = 'block'; msg.style.color = '#c44'; msg.textContent = d.error || 'Save failed.'; } } } catch (e) { if (msg) { msg.style.display = 'block'; msg.style.color = '#c44'; msg.textContent = 'Network error.'; } } }; window.deleteApiKey = async function(provider) { var sess = await sb.auth.getSession(); var token = sess.data && sess.data.session ? sess.data.session.access_token : ''; if (!token) return; try { var r = await fetch('/api/api-keys/delete', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({access_token: token, provider: provider}) }); var d = await r.json(); if (d.ok) { var maskedEl = document.getElementById('apikey-masked-' + provider); var delBtn = document.getElementById('apikey-del-' + provider); if (maskedEl) maskedEl.textContent = 'Not configured'; if (delBtn) delBtn.style.display = 'none'; var msg = document.getElementById('api-keys-msg'); if (msg) { msg.style.display = 'block'; msg.style.color = 'var(--t3)'; msg.textContent = provider.charAt(0).toUpperCase() + provider.slice(1) + ' key removed.'; } setTimeout(function() { if (msg) msg.style.display = 'none'; }, 3000); } } catch (e) {} }; function showDashboard(user) { hide('signin-section'); show('dashboard-section'); renderUserChip(user); loadWaitlistData(); loadApiKeys(); } async function init() { var result = await sb.auth.getSession(); var session = result.data && result.data.session; if (session && session.user) { showDashboard(session.user); } else { show('signin-section'); hide('dashboard-section'); } sb.auth.onAuthStateChange(function(event, session) { if (session && session.user) { showDashboard(session.user); } else { show('signin-section'); hide('dashboard-section'); } }); } init(); })()") }