180 lines
5.4 KiB
Cheetah
180 lines
5.4 KiB
Cheetah
{{define "page"}}
|
|
<header class="topbar">
|
|
<div class="container">
|
|
<div class="topbar-inner">
|
|
<a href="{{.Base}}/dashboard" class="topbar-brand">vault1984 <span class="port">account</span></a>
|
|
<nav class="topbar-nav">
|
|
<a href="{{.Base}}/dashboard" class="{{if eq .ActiveNav "dashboard"}}active{{end}}">Vaults</a>
|
|
<a href="{{.Base}}/settings" class="{{if eq .ActiveNav "settings"}}active{{end}}">Settings</a>
|
|
<div class="separator"></div>
|
|
<a href="{{.Base}}/login" id="logout-link">Sign out</a>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="container" style="flex:1">
|
|
<section class="section fade-in">
|
|
<span class="label">Account</span>
|
|
<h1 style="margin-top:0.5rem">Settings</h1>
|
|
</section>
|
|
|
|
<section class="card fade-in fade-in-delay-1">
|
|
<!-- Email -->
|
|
<div class="settings-section">
|
|
<div class="settings-row">
|
|
<div>
|
|
<div class="settings-label">Email</div>
|
|
<div class="settings-desc">johan@example.com</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Authentication -->
|
|
<div class="settings-section">
|
|
<div class="settings-row">
|
|
<div>
|
|
<div class="settings-label">Authentication</div>
|
|
<div class="settings-desc">
|
|
Passkey registered ·
|
|
<span class="text-accent" style="font-size:0.8125rem">Active</span>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-ghost" onclick="registerPasskey()">Add passkey</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Billing -->
|
|
<div class="settings-section">
|
|
<div class="settings-row">
|
|
<div>
|
|
<div class="settings-label">Billing</div>
|
|
<div class="settings-desc">
|
|
$12/year · Next charge Mar 20, 2027
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-ghost" onclick="manageBilling()">Manage in Stripe</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Invoices -->
|
|
<div class="settings-section">
|
|
<div class="settings-row">
|
|
<div>
|
|
<div class="settings-label">Invoices</div>
|
|
<div class="settings-desc">View and download past invoices</div>
|
|
</div>
|
|
<button class="btn btn-ghost" onclick="viewInvoices()">View invoices</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Danger zone -->
|
|
<section class="section fade-in fade-in-delay-2" style="padding-bottom:3rem">
|
|
<div class="danger-zone">
|
|
<h3>Danger zone</h3>
|
|
<p>
|
|
Deleting your account cancels your subscription, deletes all vaults,
|
|
and removes all data. This cannot be undone.
|
|
</p>
|
|
<button class="btn btn-danger" onclick="deleteAccount()">
|
|
Delete account
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
{{end}}
|
|
|
|
{{define "scripts"}}
|
|
<script>
|
|
function registerPasskey() {
|
|
// TODO: WebAuthn registration flow for account site
|
|
showToast('Passkey registration not yet implemented', 'error');
|
|
}
|
|
|
|
async function manageBilling() {
|
|
try {
|
|
const resp = await fetch('{{.Base}}/api/billing/portal', { method: 'POST' });
|
|
if (!resp.ok) throw new Error('Could not open billing portal');
|
|
const { url } = await resp.json();
|
|
window.location.href = url;
|
|
} catch (err) {
|
|
showToast(err.message, 'error');
|
|
}
|
|
}
|
|
|
|
function viewInvoices() {
|
|
manageBilling(); // Stripe portal handles invoices too
|
|
}
|
|
|
|
function deleteAccount() {
|
|
// Two-step confirmation
|
|
const zone = document.querySelector('.danger-zone');
|
|
if (zone.dataset.confirming) {
|
|
performDelete();
|
|
return;
|
|
}
|
|
|
|
zone.dataset.confirming = 'true';
|
|
const btn = zone.querySelector('.btn-danger');
|
|
btn.textContent = 'Confirm — delete everything';
|
|
btn.style.background = 'rgba(239,68,68,0.25)';
|
|
|
|
// Add email confirmation input
|
|
const field = document.createElement('div');
|
|
field.className = 'field';
|
|
field.style.marginBottom = '1rem';
|
|
field.innerHTML = `
|
|
<label style="color:var(--red)">Type your email to confirm</label>
|
|
<input type="email" id="delete-confirm-email" class="input" placeholder="you@example.com" style="border-color:rgba(239,68,68,0.3)">
|
|
`;
|
|
zone.insertBefore(field, btn);
|
|
document.getElementById('delete-confirm-email').focus();
|
|
}
|
|
|
|
async function performDelete() {
|
|
const email = document.getElementById('delete-confirm-email')?.value;
|
|
if (!email) {
|
|
showToast('Please enter your email to confirm', 'error');
|
|
return;
|
|
}
|
|
|
|
const btn = document.querySelector('.danger-zone .btn-danger');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Deleting…';
|
|
|
|
try {
|
|
const resp = await fetch('{{.Base}}/api/account/delete', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email })
|
|
});
|
|
if (!resp.ok) throw new Error('Could not delete account');
|
|
window.location.href = '/login?deleted=1';
|
|
} catch (err) {
|
|
showToast(err.message, 'error');
|
|
btn.disabled = false;
|
|
btn.textContent = 'Confirm — delete everything';
|
|
}
|
|
}
|
|
|
|
document.getElementById('logout-link')?.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
window.location.href = '{{.Base}}/login';
|
|
});
|
|
|
|
function showToast(msg, type) {
|
|
let t = document.querySelector('.toast');
|
|
if (!t) {
|
|
t = document.createElement('div');
|
|
t.className = 'toast';
|
|
document.body.appendChild(t);
|
|
}
|
|
t.textContent = msg;
|
|
t.className = 'toast ' + type;
|
|
requestAnimationFrame(() => t.classList.add('show'));
|
|
setTimeout(() => t.classList.remove('show'), type === 'error' ? 6000 : 3000);
|
|
}
|
|
</script>
|
|
{{end}}
|