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