feat: sessions management, web manifest, square icon-only buttons, remove lang select
- Backend: migration 002 adds user_agent/ip/last_used_at to refresh_tokens
- Backend: GET /api/auth/sessions + DELETE /api/auth/sessions/{id} endpoints
- Frontend: profile page — sessions section (browser, IP, datetime, revoke)
- Frontend: web manifest + SVG icon for PWA support
- Frontend: remove language selector from all navbars (moved to profile page)
- Frontend: neu-btn--icon-sm class for square icon-only buttons (theme/logout/edit)
- Frontend: manifest link added to all 9 HTML pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b851dc61af
commit
97212b7ffa
18 changed files with 280 additions and 42 deletions
|
|
@ -526,11 +526,15 @@ document.addEventListener('alpine:init', () => {
|
|||
theme: '',
|
||||
sidebarPosition: '',
|
||||
lang: '',
|
||||
sessions: [],
|
||||
sessionsLoading: true,
|
||||
revoking: {},
|
||||
|
||||
init() {
|
||||
async init() {
|
||||
this.theme = Alpine.store('ui').theme
|
||||
this.sidebarPosition = Alpine.store('ui').sidebarPosition
|
||||
this.lang = Alpine.store('i18n').lang
|
||||
await this.loadSessions()
|
||||
},
|
||||
|
||||
setTheme(t) {
|
||||
|
|
@ -550,6 +554,40 @@ document.addEventListener('alpine:init', () => {
|
|||
await Alpine.store('i18n').setLang(lang)
|
||||
},
|
||||
|
||||
async loadSessions() {
|
||||
this.sessionsLoading = true
|
||||
try {
|
||||
const res = await apiFetch('/api/auth/sessions')
|
||||
if (res.ok) this.sessions = await res.json()
|
||||
} catch (e) { /* ignore */ }
|
||||
this.sessionsLoading = false
|
||||
},
|
||||
|
||||
async revokeSession(id) {
|
||||
this.revoking = { ...this.revoking, [id]: true }
|
||||
try {
|
||||
const res = await apiFetch(`/api/auth/sessions/${id}`, { method: 'DELETE' })
|
||||
if (res.ok) this.sessions = this.sessions.filter(s => s.id !== id)
|
||||
} catch (e) { /* ignore */ }
|
||||
this.revoking = { ...this.revoking, [id]: false }
|
||||
},
|
||||
|
||||
formatDate(iso) {
|
||||
if (!iso) return '—'
|
||||
const d = new Date(iso)
|
||||
return d.toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })
|
||||
},
|
||||
|
||||
parseUA(ua) {
|
||||
if (!ua) return 'Navigateur inconnu'
|
||||
if (/Firefox\/(\d+)/i.test(ua)) return `Firefox ${RegExp.$1}`
|
||||
if (/Chrome\/(\d+)/i.test(ua) && !/Chromium|Edge|OPR/i.test(ua)) return `Chrome ${RegExp.$1}`
|
||||
if (/Edg\/(\d+)/i.test(ua)) return `Edge ${RegExp.$1}`
|
||||
if (/Safari\//i.test(ua) && !/Chrome/i.test(ua)) return 'Safari'
|
||||
if (/OPR\/(\d+)/i.test(ua)) return `Opera ${RegExp.$1}`
|
||||
return ua.slice(0, 40)
|
||||
},
|
||||
|
||||
get user() { return Alpine.store('auth').user },
|
||||
t(key) { return Alpine.store('i18n').t(key) },
|
||||
}))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue