refactor(admin): split sso page.tsx into colocated components
Extract types, constants, helpers, and UI pieces (shared LoadingSkeleton/ EmptyState/StatusBadge/CopyButton, SSOConfigFormModal, DeleteConfirmModal, ConnectionTestPanel, SSOConfigCard, SSOUsersTable, SSOInfoSection) into _components/ and _types.ts to bring page.tsx from 1482 LOC to 339 LOC (under the 500 hard cap). Behavior preserved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
117
admin-compliance/app/sdk/sso/_components/ConnectionTestPanel.tsx
Normal file
117
admin-compliance/app/sdk/sso/_components/ConnectionTestPanel.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
CheckCircle2,
|
||||
ExternalLink,
|
||||
Loader2,
|
||||
RefreshCw,
|
||||
TestTube,
|
||||
XCircle,
|
||||
} from 'lucide-react'
|
||||
import type { ConnectionTestResult, SSOConfig } from '../_types'
|
||||
import { API_BASE } from './constants'
|
||||
import { apiFetch, getTenantId } from './helpers'
|
||||
|
||||
export function ConnectionTestPanel({ config }: { config: SSOConfig }) {
|
||||
const [testing, setTesting] = useState(false)
|
||||
const [result, setResult] = useState<ConnectionTestResult | null>(null)
|
||||
|
||||
const runTest = async () => {
|
||||
setTesting(true)
|
||||
setResult(null)
|
||||
try {
|
||||
const data = await apiFetch<ConnectionTestResult>(`/configs/${config.id}/test`, {
|
||||
method: 'POST',
|
||||
})
|
||||
setResult(data)
|
||||
} catch (err: unknown) {
|
||||
setResult({
|
||||
success: false,
|
||||
message: err instanceof Error ? err.message : 'Verbindungstest fehlgeschlagen',
|
||||
})
|
||||
} finally {
|
||||
setTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const openLoginFlow = () => {
|
||||
const loginUrl = `${API_BASE}/configs/${config.id}/login?tenant_id=${getTenantId()}`
|
||||
window.open(loginUrl, '_blank', 'width=600,height=700')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border border-slate-200 rounded-lg p-4 bg-slate-50">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h4 className="text-sm font-medium text-slate-700 flex items-center gap-2">
|
||||
<TestTube className="w-4 h-4" />
|
||||
Verbindungstest
|
||||
</h4>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={runTest}
|
||||
disabled={testing}
|
||||
className="px-3 py-1.5 text-xs text-purple-600 border border-purple-300 rounded-lg hover:bg-purple-50 transition-colors flex items-center gap-1 disabled:opacity-50"
|
||||
>
|
||||
{testing ? <Loader2 className="w-3 h-3 animate-spin" /> : <RefreshCw className="w-3 h-3" />}
|
||||
Discovery testen
|
||||
</button>
|
||||
<button
|
||||
onClick={openLoginFlow}
|
||||
className="px-3 py-1.5 text-xs text-white bg-purple-600 rounded-lg hover:bg-purple-700 transition-colors flex items-center gap-1"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
Login testen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{result && (
|
||||
<div className={`p-3 rounded-lg text-sm ${
|
||||
result.success
|
||||
? 'bg-green-50 border border-green-200 text-green-700'
|
||||
: 'bg-red-50 border border-red-200 text-red-700'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 font-medium mb-1">
|
||||
{result.success ? (
|
||||
<CheckCircle2 className="w-4 h-4" />
|
||||
) : (
|
||||
<XCircle className="w-4 h-4" />
|
||||
)}
|
||||
{result.message}
|
||||
</div>
|
||||
{result.details && (
|
||||
<div className="mt-2 space-y-1 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
{result.details.issuer_reachable ? (
|
||||
<CheckCircle2 className="w-3 h-3 text-green-500" />
|
||||
) : (
|
||||
<XCircle className="w-3 h-3 text-red-500" />
|
||||
)}
|
||||
Issuer erreichbar
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{result.details.jwks_available ? (
|
||||
<CheckCircle2 className="w-3 h-3 text-green-500" />
|
||||
) : (
|
||||
<XCircle className="w-3 h-3 text-red-500" />
|
||||
)}
|
||||
JWKS verfuegbar
|
||||
</div>
|
||||
{result.details.authorization_endpoint && (
|
||||
<div className="flex items-center gap-2 text-slate-500 font-mono truncate">
|
||||
Authorization: {result.details.authorization_endpoint}
|
||||
</div>
|
||||
)}
|
||||
{result.details.token_endpoint && (
|
||||
<div className="flex items-center gap-2 text-slate-500 font-mono truncate">
|
||||
Token: {result.details.token_endpoint}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user