import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Plus, Edit2, Trash2, Globe, Check, X, RefreshCw, ExternalLink } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { api } from '../api/client'; import type { OIDCProvider, OIDCProviderCreate } from '../api/client'; import { Card, CardContent, CardHeader } from './Card'; import { Button } from './Button'; import { Toggle } from './Toggle'; import { ConfirmModal } from './ConfirmModal'; import { useToast } from '../contexts/ToastContext'; const EMPTY_FORM: OIDCProviderCreate = { name: '', issuer_url: '', client_id: '', client_secret: '', scopes: 'openid email profile', is_enabled: true, auto_create_users: false, icon_url: undefined, }; // ─── Provider form (create / edit) ─────────────────────────────────────────── function ProviderForm({ initial, isEdit = false, onSave, onCancel, isPending, }: { initial: OIDCProviderCreate; isEdit?: boolean; onSave: (data: OIDCProviderCreate) => void; onCancel: () => void; isPending: boolean; }) { const { t } = useTranslation(); const [form, setForm] = useState(initial); const [secretChanged, setSecretChanged] = useState(false); const set = (key: keyof OIDCProviderCreate, value: unknown) => setForm((prev) => ({ ...prev, [key]: value })); const inputCls = 'w-full px-4 py-3 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:outline-none focus:ring-2 focus:ring-bambu-green/50 focus:border-bambu-green transition-colors text-sm'; const labelCls = 'block text-sm font-medium text-white mb-1'; const handleSave = () => { const payload = { ...form }; if (isEdit && !secretChanged) { delete (payload as Partial).client_secret; } onSave(payload); }; return (
set('name', e.target.value)} placeholder="Google" />
set('issuer_url', e.target.value)} placeholder="https://accounts.google.com" />
set('client_id', e.target.value)} placeholder="your-client-id" />
{ setSecretChanged(true); set('client_secret', e.target.value); }} />
set('scopes', e.target.value)} placeholder="openid email profile" />
set('icon_url', e.target.value || undefined)} placeholder="https://..." />
); } // ─── Main component ─────────────────────────────────────────────────────────── export function OIDCProviderSettings() { const { t } = useTranslation(); const queryClient = useQueryClient(); const { showToast } = useToast(); const [showCreate, setShowCreate] = useState(false); const [editingId, setEditingId] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const { data: providers, isLoading } = useQuery({ queryKey: ['oidc-providers-all'], queryFn: () => api.getOIDCProvidersAll(), }); const createMutation = useMutation({ mutationFn: (data: OIDCProviderCreate) => api.createOIDCProvider(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['oidc-providers-all'] }); setShowCreate(false); showToast(t('settings.oidc.created'), 'success'); }, onError: (e: Error) => showToast(e.message, 'error'), }); const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: number; data: Partial }) => api.updateOIDCProvider(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['oidc-providers-all'] }); setEditingId(null); showToast(t('settings.oidc.updated'), 'success'); }, onError: (e: Error) => showToast(e.message, 'error'), }); const deleteMutation = useMutation({ mutationFn: (id: number) => api.deleteOIDCProvider(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['oidc-providers-all'] }); setDeleteTarget(null); showToast(t('settings.oidc.deleted'), 'success'); }, onError: (e: Error) => showToast(e.message, 'error'), }); const toggleEnabled = (provider: OIDCProvider) => updateMutation.mutate({ id: provider.id, data: { is_enabled: !provider.is_enabled } }); if (isLoading) { return (
); } return (
{/* Header */}

{t('settings.oidc.title')}

{t('settings.oidc.desc')}

{!showCreate && ( )}
{showCreate && (

{t('settings.oidc.newProvider')}

createMutation.mutate(data)} onCancel={() => setShowCreate(false)} isPending={createMutation.isPending} />
)}
{/* Provider list */} {providers && providers.length === 0 && !showCreate && (

{t('settings.oidc.empty')}

)} {providers?.map((provider) => (
{provider.icon_url ? ( {provider.name} { (e.target as HTMLImageElement).style.display = 'none'; }} /> ) : (
)}

{provider.name}

{provider.is_enabled ? ( {t('common.enabled')} ) : ( {t('common.disabled')} )}
{provider.issuer_url}
toggleEnabled(provider)} disabled={updateMutation.isPending} />
{editingId === provider.id && (
updateMutation.mutate({ id: provider.id, data })} onCancel={() => setEditingId(null)} isPending={updateMutation.isPending} />
)} {editingId !== provider.id && (
{t('settings.oidc.form.clientId')}
{provider.client_id}
{t('settings.oidc.form.scopes')}
{provider.scopes}
{t('settings.oidc.form.autoCreate')}
{provider.auto_create_users ? t('common.yes') : t('common.no')}
)}
))} {/* Delete confirm */} {deleteTarget && ( deleteMutation.mutate(deleteTarget.id)} onCancel={() => setDeleteTarget(null)} /> )}
); }