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:
158
admin-compliance/app/sdk/sso/_components/SSOConfigCard.tsx
Normal file
158
admin-compliance/app/sdk/sso/_components/SSOConfigCard.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Key,
|
||||
Pencil,
|
||||
Trash2,
|
||||
} from 'lucide-react'
|
||||
import type { SSOConfig } from '../_types'
|
||||
import { AVAILABLE_ROLES } from './constants'
|
||||
import { formatDate } from './helpers'
|
||||
import { CopyButton, StatusBadge } from './shared'
|
||||
import { ConnectionTestPanel } from './ConnectionTestPanel'
|
||||
|
||||
export function SSOConfigCard({
|
||||
config,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onToggle,
|
||||
}: {
|
||||
config: SSOConfig
|
||||
onEdit: () => void
|
||||
onDelete: () => void
|
||||
onToggle: () => void
|
||||
}) {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-slate-200 hover:border-purple-300 transition-colors">
|
||||
{/* Main Row */}
|
||||
<div className="p-5">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-4 flex-1 min-w-0">
|
||||
<div className="w-10 h-10 bg-purple-100 text-purple-700 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<Key className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<h3 className="font-semibold text-slate-900">{config.name}</h3>
|
||||
<span className="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs font-medium uppercase">
|
||||
{config.provider_type}
|
||||
</span>
|
||||
<StatusBadge enabled={config.enabled} />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500 font-mono truncate">{config.oidc_issuer_url}</p>
|
||||
<div className="flex items-center gap-4 mt-2 text-xs text-slate-400">
|
||||
<span>Client ID: {(config.oidc_client_id || '').substring(0, 12)}...</span>
|
||||
<span>Scopes: {(config.oidc_scopes || []).join(', ')}</span>
|
||||
<span>Erstellt: {formatDate(config.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 ml-4 flex-shrink-0">
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
||||
config.enabled ? 'bg-purple-600' : 'bg-slate-300'
|
||||
}`}
|
||||
title={config.enabled ? 'Deaktivieren' : 'Aktivieren'}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
config.enabled ? 'translate-x-6' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={onEdit}
|
||||
className="p-2 text-slate-400 hover:text-purple-600 transition-colors"
|
||||
title="Bearbeiten"
|
||||
>
|
||||
<Pencil className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={onDelete}
|
||||
className="p-2 text-slate-400 hover:text-red-500 transition-colors"
|
||||
title="Loeschen"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="p-2 text-slate-400 hover:text-slate-600 transition-colors"
|
||||
title={expanded ? 'Zuklappen' : 'Details anzeigen'}
|
||||
>
|
||||
{expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expanded Details */}
|
||||
{expanded && (
|
||||
<div className="px-5 pb-5 space-y-4 border-t border-slate-100 pt-4">
|
||||
{/* Config Details Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-slate-500">Redirect URI:</span>
|
||||
<div className="flex items-center gap-1 mt-0.5">
|
||||
<span className="font-mono text-slate-700 truncate text-xs">{config.oidc_redirect_uri}</span>
|
||||
<CopyButton value={config.oidc_redirect_uri} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-slate-500">Auto-Provisioning:</span>
|
||||
<div className="mt-0.5 text-slate-700">
|
||||
{config.auto_provision ? 'Aktiviert' : 'Deaktiviert'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-slate-500">Standard-Rolle:</span>
|
||||
<div className="mt-0.5 text-slate-700">
|
||||
{config.default_role_id || 'Standard'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-slate-500">Scopes:</span>
|
||||
<div className="flex gap-1 mt-0.5 flex-wrap">
|
||||
{(config.oidc_scopes || []).map(scope => (
|
||||
<span key={scope} className="px-2 py-0.5 bg-slate-100 text-slate-600 rounded text-xs">
|
||||
{scope}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Role Mappings */}
|
||||
{config.role_mapping && Object.keys(config.role_mapping).length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-slate-700 mb-2">Rollen-Mapping</h4>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(config.role_mapping).map(([group, role]) => (
|
||||
<div key={group} className="flex items-center gap-2 text-sm">
|
||||
<span className="px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs font-mono">
|
||||
{group}
|
||||
</span>
|
||||
<span className="text-slate-400">→</span>
|
||||
<span className="px-2 py-0.5 bg-purple-100 text-purple-700 rounded text-xs">
|
||||
{AVAILABLE_ROLES.find(r => r.value === role)?.label || role}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Connection Test */}
|
||||
<ConnectionTestPanel config={config} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user