separate account settings from shop settings
All checks were successful
deploy / deploy (push) Successful in 3m28s

- Create dedicated /admin/account page for user account management
- Move email, password, and 2FA settings from /admin/settings
- Add Account link to top of admin sidebar navigation
- Add TOTP-based two-factor authentication with NimbleTOTP
- Add TOTP verification LiveView for login flow
- Add AccountController for TOTP session management
- Remove Advanced section from settings (duplicated in dev tools)
- Remove user email from sidebar footer (replaced by Account link)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-08 18:42:29 +00:00
parent 0c2d4ac406
commit 32cc425458
21 changed files with 1396 additions and 308 deletions

View File

@@ -735,6 +735,46 @@ const EditorSheet = {
}
}
// Clipboard: copy text from a target element to clipboard
const Clipboard = {
mounted() {
this.el.addEventListener("click", () => {
const targetId = this.el.dataset.copyTarget
const target = document.getElementById(targetId)
if (!target) return
const text = target.textContent.trim()
this._copyText(text).then(() => {
const span = this.el.querySelector("span")
if (span) {
const original = span.textContent
span.textContent = "Copied!"
setTimeout(() => { span.textContent = original }, 1500)
}
})
})
},
_copyText(text) {
// Modern API (requires HTTPS or localhost)
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text)
}
// Fallback for HTTP contexts
return new Promise((resolve) => {
const textarea = document.createElement("textarea")
textarea.value = text
textarea.style.position = "fixed"
textarea.style.opacity = "0"
document.body.appendChild(textarea)
textarea.select()
document.execCommand("copy")
document.body.removeChild(textarea)
resolve()
})
}
}
// DirtyGuard + Ctrl+Z / Ctrl+Shift+Z undo/redo for page editors
const EditorKeyboard = {
mounted() {
@@ -788,7 +828,7 @@ const EditorKeyboard = {
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken, screen_width: window.innerWidth},
hooks: {...colocatedHooks, ColorSync, Lightbox, CartPersist, CartDrawer, ProductImageScroll, SearchModal, MobileNavDrawer, CollectionFilters, AnalyticsInit, AnalyticsExport, ChartTooltip, DirtyGuard, EditorKeyboard, EditorSheet},
hooks: {...colocatedHooks, ColorSync, Lightbox, CartPersist, CartDrawer, ProductImageScroll, SearchModal, MobileNavDrawer, CollectionFilters, AnalyticsInit, AnalyticsExport, ChartTooltip, Clipboard, DirtyGuard, EditorKeyboard, EditorSheet},
})
// Show progress bar on live navigation and form submits