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:
Sharang Parnerkar
2026-04-11 22:53:08 +02:00
parent 1f45d6cca8
commit 2fb6b98bc5
11 changed files with 1174 additions and 1157 deletions

View 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>
)
}