Move allauth + auth UI to legacy/
allauth/ (44 files) is a django-allauth React UI — a separate concern from the Mizan protocol. Moved to legacy/ pending extraction into a standalone mizan-django-allauth package. Also moved to legacy/: - client/AuthContext.tsx — generic auth state from /me endpoint - client/RouterContext.tsx — framework-agnostic router adapter - client/routing.tsx — UserRoute/StaffRoute/AnonymousRoute guards - client/nextjs.tsx — Next.js router adapter for auth These are auth UI infrastructure, not Mizan protocol. The Mizan core only needs JWT for auth header selection (jwt/ stays — MizanProvider depends on useJWT() to decide between Bearer and session auth). Cleaned up re-exports in client/react.ts and vitest aliases. 33 React tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
legacy/allauth/components/settings/SessionsSection.tsx
Normal file
88
legacy/allauth/components/settings/SessionsSection.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useAllauthAPI } from '../../contexts/APIContext'
|
||||
import { SettingsSection, SettingsItem, SettingsList, Badge, Button } from './SettingsComponents'
|
||||
import type { Session } from '../../types'
|
||||
|
||||
function parseUserAgent(ua: string): string {
|
||||
if (ua.includes('Chrome')) return 'Chrome'
|
||||
if (ua.includes('Firefox')) return 'Firefox'
|
||||
if (ua.includes('Safari')) return 'Safari'
|
||||
if (ua.includes('Edge')) return 'Edge'
|
||||
return 'Unknown Browser'
|
||||
}
|
||||
|
||||
export function SessionsSection() {
|
||||
const api = useAllauthAPI()
|
||||
const [sessions, setSessions] = useState<Session[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [available, setAvailable] = useState(true)
|
||||
|
||||
const fetchSessions = async () => {
|
||||
try {
|
||||
const res = await api.session.list()
|
||||
if (res.status === 200 && res.data) {
|
||||
setSessions(res.data as Session[])
|
||||
} else {
|
||||
// Non-200 status means sessions feature not available
|
||||
setAvailable(false)
|
||||
}
|
||||
} catch {
|
||||
setAvailable(false)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
useEffect(() => { fetchSessions() }, [])
|
||||
|
||||
const handleEnd = async (id: number) => {
|
||||
if (!confirm('End this session?')) return
|
||||
await api.session.remove([id])
|
||||
fetchSessions()
|
||||
}
|
||||
|
||||
const handleEndAllOthers = async () => {
|
||||
const otherIds = sessions.filter(s => !s.is_current).map(s => s.id)
|
||||
if (otherIds.length === 0) return
|
||||
if (!confirm(`End ${otherIds.length} other session(s)?`)) return
|
||||
await api.session.remove(otherIds)
|
||||
fetchSessions()
|
||||
}
|
||||
|
||||
if (loading || !available) return null
|
||||
|
||||
const otherSessions = sessions.filter(s => !s.is_current)
|
||||
|
||||
return (
|
||||
<SettingsSection title="Active Sessions">
|
||||
<SettingsList>
|
||||
{sessions.map(session => (
|
||||
<SettingsItem
|
||||
key={session.id}
|
||||
label={
|
||||
<>
|
||||
{parseUserAgent(session.user_agent)}
|
||||
{session.is_current && <Badge variant="success">Current</Badge>}
|
||||
</>
|
||||
}
|
||||
meta={`${session.ip} · ${session.last_seen_at ? new Date(session.last_seen_at * 1000).toLocaleString() : 'Unknown'}`}
|
||||
actions={
|
||||
!session.is_current && (
|
||||
<Button variant="danger" onClick={() => handleEnd(session.id)}>
|
||||
End
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</SettingsList>
|
||||
|
||||
{otherSessions.length > 0 && (
|
||||
<Button variant="danger" onClick={handleEndAllOthers}>
|
||||
End All Other Sessions
|
||||
</Button>
|
||||
)}
|
||||
</SettingsSection>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user