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}}