'use client' import { useTranslations } from 'next-intl' import { useState, useEffect, useCallback } from 'react' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog' import { useToast } from '@/components/ui/use-toast' import { Loader2, Plus, Trash2, Globe } from 'lucide-react' interface OAuthClient { id: string client_name: string redirect_uri: string created_at: string revoked_at: string | null } export function OAuthClientsPanel() { const t = useTranslations('settings_oauth_clients') const { toast } = useToast() const { dialogProps: revokeDialogProps, confirm: confirmRevoke } = useDestructiveConfirm() const [clients, setClients] = useState([]) const [isLoading, setIsLoading] = useState(true) const [isCreating, setIsCreating] = useState(false) const [showCreateDialog, setShowCreateDialog] = useState(false) const [clientName, setClientName] = useState('') const [redirectUri, setRedirectUri] = useState('') const fetchClients = useCallback(async () => { try { const res = await fetch('/api/settings/oauth-clients') const json = await res.json() if (json.data) { setClients(json.data.filter((c: OAuthClient) => !c.revoked_at)) } } catch { toast({ title: t('toast_fetch_failed'), variant: 'destructive' }) } finally { setIsLoading(false) } }, [toast, t]) useEffect(() => { fetchClients() }, [fetchClients]) async function handleCreate() { setIsCreating(true) try { const res = await fetch('/api/settings/oauth-clients', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_name: clientName.trim() || t('default_client_name'), redirect_uri: redirectUri.trim(), }), }) const json = await res.json() if (!res.ok) { toast({ title: json.error ?? t('toast_register_failed'), variant: 'destructive' }) return } setShowCreateDialog(false) setClientName('') setRedirectUri('') fetchClients() } catch { toast({ title: t('toast_register_failed'), variant: 'destructive' }) } finally { setIsCreating(false) } } async function handleRevoke(id: string, name: string) { const ok = await confirmRevoke({ title: t('revoke_dialog_title'), description: t('revoke_dialog_description', { name }), confirmLabel: t('revoke_confirm'), }) if (!ok) return try { const res = await fetch(`/api/settings/oauth-clients/${id}`, { method: 'DELETE' }) if (!res.ok) { const body = await res.json().catch(() => ({})) toast({ title: body?.error || t('toast_revoke_failed'), variant: 'destructive', }) return } setClients((prev) => prev.filter((c) => c.id !== id)) toast({ title: t('toast_revoked') }) } catch { toast({ title: t('toast_revoke_failed'), variant: 'destructive' }) } } function formatDate(iso: string) { return new Date(iso).toLocaleDateString('sv-SE', { year: 'numeric', month: 'short', day: 'numeric', }) } return (
{t('title')} {t('description')}
{isLoading ? (
) : clients.length === 0 ? (

{t('empty_title')}

{t('empty_help')}

) : (
{clients.map((c) => (

{c.client_name}

{c.redirect_uri} {t('registered_on')} {formatDate(c.created_at)}
))}
)}
{t('register_dialog_title')} {t.rich('register_dialog_description', { bold: (chunks) => {chunks}, code: (chunks) => {chunks}, })}
setClientName(e.target.value)} />
setRedirectUri(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && redirectUri && handleCreate()} />
) }