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>
88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import { useConfig } from '../../contexts/AuthContext'
|
|
import { useAllauthAPI } from '../../contexts/APIContext'
|
|
import { SettingsSection, SettingsItem, SettingsList, Button } from './SettingsComponents'
|
|
|
|
interface Connection {
|
|
uid: string
|
|
provider: { id: string; name: string }
|
|
display: string
|
|
}
|
|
|
|
interface ConnectionsSectionProps {
|
|
/** URL to redirect back to after OAuth connect */
|
|
redirectUrl?: string
|
|
}
|
|
|
|
export function ConnectionsSection({ redirectUrl = '/account' }: ConnectionsSectionProps) {
|
|
const api = useAllauthAPI()
|
|
const config = useConfig()
|
|
const [connections, setConnections] = useState<Connection[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
|
|
const availableProviders = config?.data?.socialaccount?.providers || []
|
|
|
|
const fetchConnections = async () => {
|
|
const res = await api.oauth.list()
|
|
if (res.status === 200 && res.data) {
|
|
setConnections(res.data)
|
|
}
|
|
setLoading(false)
|
|
}
|
|
|
|
useEffect(() => { fetchConnections() }, [])
|
|
|
|
const handleConnect = (providerId: string) => {
|
|
api.oauth.provider(providerId).connect.withRedirect(redirectUrl)
|
|
}
|
|
|
|
const handleDisconnect = async (providerId: string, uid: string) => {
|
|
if (!confirm('Disconnect this account?')) return
|
|
await api.oauth.provider(providerId).removeFrom(uid)
|
|
fetchConnections()
|
|
}
|
|
|
|
// Don't render if no providers configured or still loading
|
|
if (loading) return null
|
|
|
|
const connectedProviderIds = connections.map(c => c.provider.id)
|
|
const unconnectedProviders = availableProviders.filter(
|
|
(p: { id: string }) => !connectedProviderIds.includes(p.id)
|
|
)
|
|
|
|
// Hide section entirely if no social providers
|
|
if (connections.length === 0 && availableProviders.length === 0) return null
|
|
|
|
return (
|
|
<SettingsSection title="Connected Accounts">
|
|
<SettingsList>
|
|
{connections.map(conn => (
|
|
<SettingsItem
|
|
key={conn.uid}
|
|
label={conn.provider.name}
|
|
meta={conn.display}
|
|
actions={
|
|
<Button variant="danger" onClick={() => handleDisconnect(conn.provider.id, conn.uid)}>
|
|
Disconnect
|
|
</Button>
|
|
}
|
|
/>
|
|
))}
|
|
{unconnectedProviders.map((provider: { id: string; name: string }) => (
|
|
<SettingsItem
|
|
key={provider.id}
|
|
label={provider.name}
|
|
actions={
|
|
<Button onClick={() => handleConnect(provider.id)}>
|
|
Connect
|
|
</Button>
|
|
}
|
|
/>
|
|
))}
|
|
</SettingsList>
|
|
</SettingsSection>
|
|
)
|
|
}
|