2025-08-25 21:19:15 +01:00
{{define "profile"}}
{{template "base" .}}
{{end}}
{{define "profile_content"}}
< div class = "max-w-3xl mx-auto p-6" >
< div class = "mb-8" >
< h1 class = "text-3xl font-bold text-white mb-2" > Your Profile< / h1 >
< p class = "text-gray-400" > Manage your account details and security< / p >
< / div >
< div class = "space-y-8" >
<!-- Email -->
< div class = "bg-gray-800 rounded-lg p-6" >
< h2 class = "text-xl font-semibold text-white mb-4" >
< i class = "fas fa-envelope mr-2" > < / i > Email< / h2 >
< form id = "email-form" class = "space-y-4" >
< div >
< label for = "email" class = "block text-sm font-medium text-gray-300 mb-2" > Email< / label >
< input type = "email" id = "email" name = "email" value = "{{.Email}}"
class="form-input" placeholder="you@example.com" required>
< / div >
< div class = "flex justify-end" >
< button type = "submit" class = "btn-primary" >
< i class = "fas fa-save mr-2" > < / i > Update Email
< / button >
< / div >
< / form >
< / div >
<!-- Password -->
< div class = "bg-gray-800 rounded-lg p-6" >
< h2 class = "text-xl font-semibold text-white mb-4" >
< i class = "fas fa-key mr-2" > < / i > Change Password< / h2 >
< form id = "password-form" class = "space-y-4" >
< div >
< label for = "current_password" class = "block text-sm font-medium text-gray-300 mb-2" > Current Password< / label >
< input type = "password" id = "current_password" name = "current_password" class = "form-input" required >
< / div >
< div class = "grid grid-cols-1 md:grid-cols-2 gap-4" >
< div >
< label for = "new_password" class = "block text-sm font-medium text-gray-300 mb-2" > New Password< / label >
< input type = "password" id = "new_password" name = "new_password" class = "form-input" minlength = "8" required >
< / div >
< div >
< label for = "confirm_password" class = "block text-sm font-medium text-gray-300 mb-2" > Confirm New Password< / label >
< input type = "password" id = "confirm_password" name = "confirm_password" class = "form-input" minlength = "8" required >
< / div >
< / div >
< div class = "flex justify-end" >
< button type = "submit" class = "btn-primary" >
< i class = "fas fa-save mr-2" > < / i > Change Password
< / button >
< / div >
< / form >
< / div >
<!-- MFA -->
< div class = "bg-gray-800 rounded-lg p-6" >
< h2 class = "text-xl font-semibold text-white mb-4" >
< i class = "fas fa-shield-halved mr-2" > < / i > Multi‑ Factor Authentication< / h2 >
< div class = "flex items-center justify-between" >
< div >
< div class = "text-white font-medium" > Status: < span id = "mfa-status" class = "ml-1" > {{if .MFAEnabled}}Enabled{{else}}Disabled{{end}}< / span > < / div >
< div class = "text-sm text-gray-400" > Add an extra layer of security to your account< / div >
< / div >
< div class = "space-x-2" >
< button id = "mfa-enable" class = "btn-primary {{if .MFAEnabled}}hidden{{end}}" > < i class = "fas fa-toggle-on mr-2" > < / i > Enable< / button >
< button id = "mfa-disable" class = "btn-danger {{if not .MFAEnabled}}hidden{{end}}" > < i class = "fas fa-toggle-off mr-2" > < / i > Disable< / button >
< / div >
< / div >
< p class = "text-xs text-gray-500 mt-3" > Note: This simple flow generates or clears your MFA secret. A full QR/TOTP enrollment flow can be added later.< / p >
< / div >
< / div >
< / div >
{{end}}
{{define "profile_scripts"}}
< script >
function getCSRF() {
const m = document.cookie.match(/(?:^|; )csrf_token=([^;]+)/);
return m & & m[1] ? decodeURIComponent(m[1]) : '';
}
function formToJSON(form) {
const fd = new FormData(form);
return new URLSearchParams(Array.from(fd.entries()));
}
document.addEventListener('DOMContentLoaded', () => {
const emailForm = document.getElementById('email-form');
const passwordForm = document.getElementById('password-form');
const btnEnable = document.getElementById('mfa-enable');
const btnDisable = document.getElementById('mfa-disable');
const mfaStatus = document.getElementById('mfa-status');
if (emailForm) emailForm.addEventListener('submit', async (e) => {
e.preventDefault();
try {
2025-08-26 20:55:08 +01:00
const res = await fetch(window.prefix('/editor/profile/email'), {
2025-08-25 21:19:15 +01:00
method: 'POST',
headers: Object.assign({'X-CSRF-Token': getCSRF()}),
body: formToJSON(emailForm)
});
const data = await res.json().catch(() => ({}));
if (res.ok & & data.success) {
showNotification('Email updated', 'success');
} else {
throw new Error(data.error || res.statusText);
}
} catch (err) {
showNotification('Update failed: ' + err.message, 'error');
}
});
if (passwordForm) passwordForm.addEventListener('submit', async (e) => {
e.preventDefault();
const newpw = document.getElementById('new_password').value;
const conf = document.getElementById('confirm_password').value;
if (newpw !== conf) {
showNotification('New passwords do not match', 'error');
return;
}
try {
2025-08-26 20:55:08 +01:00
const res = await fetch(window.prefix('/editor/profile/password'), {
2025-08-25 21:19:15 +01:00
method: 'POST',
headers: Object.assign({'X-CSRF-Token': getCSRF()}),
body: formToJSON(passwordForm)
});
const data = await res.json().catch(() => ({}));
if (res.ok & & data.success) {
passwordForm.reset();
showNotification('Password changed', 'success');
} else {
throw new Error(data.error || res.statusText);
}
} catch (err) {
showNotification('Password change failed: ' + err.message, 'error');
}
});
async function toggleMFA(enable) {
try {
const url = enable ? '/editor/profile/mfa/enable' : '/editor/profile/mfa/disable';
2025-08-26 20:55:08 +01:00
const res = await fetch(window.prefix(url), { method: 'POST', headers: {'X-CSRF-Token': getCSRF()} });
2025-08-25 21:19:15 +01:00
const data = await res.json().catch(() => ({}));
if (res.ok & & data.success) {
if (enable) {
if (data.setup & & data.redirect) {
// Enrollment flow: go to setup page; do not toggle UI yet
window.location.href = data.redirect;
return;
}
// Direct enable (no setup)
btnEnable.classList.add('hidden');
btnDisable.classList.remove('hidden');
mfaStatus.textContent = 'Enabled';
} else {
btnDisable.classList.add('hidden');
btnEnable.classList.remove('hidden');
mfaStatus.textContent = 'Disabled';
}
showNotification('MFA ' + (enable ? 'enabled' : 'disabled'), 'success');
} else {
throw new Error(data.error || res.statusText);
}
} catch (err) {
showNotification('MFA update failed: ' + err.message, 'error');
}
}
if (btnEnable) btnEnable.addEventListener('click', (e) => { e.preventDefault(); toggleMFA(true); });
if (btnDisable) btnDisable.addEventListener('click', (e) => { e.preventDefault(); toggleMFA(false); });
});
< / script >
{{end}}