import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { ArrowLeft, Link2, Plus, Trash2 } from "lucide-react"; import { useMemo, useState } from "react"; import { Link, useParams } from "react-router-dom"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "../../components/ui/card"; import { Input } from "../../components/ui/input"; import { Label } from "../../components/ui/label"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../components/ui/table"; import { toast } from "../../components/ui/use-toast"; import { addClientRelation, fetchClient, fetchClientRelations, removeClientRelation, } from "../../lib/devApi"; import { t } from "../../lib/i18n"; const relationOptions = [ "admins", "creator", "config_editor", "secret_rotator", "jwks_viewer", "jwks_operator", "consent_viewer", "consent_revoker", "relationship_viewer", "status_operator", ] as const; function ClientRelationsPage() { const params = useParams(); const queryClient = useQueryClient(); const clientId = params.id ?? ""; const [relation, setRelation] = useState<(typeof relationOptions)[number]>( "config_editor", ); const [userId, setUserId] = useState(""); const { data: clientData } = useQuery({ queryKey: ["client", clientId], queryFn: () => fetchClient(clientId), enabled: clientId.length > 0, }); const { data: relationData, isLoading, error, } = useQuery({ queryKey: ["client-relations", clientId], queryFn: () => fetchClientRelations(clientId), enabled: clientId.length > 0, }); const sortedItems = useMemo(() => { return [...(relationData?.items ?? [])].sort((a, b) => { const relationCompare = a.relation.localeCompare(b.relation); if (relationCompare !== 0) { return relationCompare; } return a.subject.localeCompare(b.subject); }); }, [relationData?.items]); const addMutation = useMutation({ mutationFn: () => addClientRelation(clientId, { relation, userId: userId.trim(), }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["client-relations", clientId] }); setUserId(""); toast( t( "msg.dev.clients.relationships.added", "Relationship가 추가되었습니다.", ), ); }, onError: (err) => { toast( t( "msg.dev.clients.relationships.add_error", "Relationship 추가 실패: {{error}}", { error: (err as AxiosError<{ error?: string }>).response?.data?.error ?? (err as Error).message, }, ), "error", ); }, }); const removeMutation = useMutation({ mutationFn: (payload: { relation: string; subject: string }) => removeClientRelation(clientId, payload.relation, payload.subject), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["client-relations", clientId] }); toast( t( "msg.dev.clients.relationships.removed", "Relationship가 제거되었습니다.", ), ); }, onError: (err) => { toast( t( "msg.dev.clients.relationships.remove_error", "Relationship 제거 실패: {{error}}", { error: (err as AxiosError<{ error?: string }>).response?.data?.error ?? (err as Error).message, }, ), "error", ); }, }); const handleAdd = () => { if (!userId.trim()) { toast( t( "msg.dev.clients.relationships.user_required", "추가할 User ID를 입력하세요.", ), "error", ); return; } addMutation.mutate(); }; const handleRemove = (targetRelation: string, subject: string) => { if ( window.confirm( t( "msg.dev.clients.relationships.remove_confirm", "이 relationship를 제거하시겠습니까?", ), ) ) { removeMutation.mutate({ relation: targetRelation, subject }); } }; if (!clientId) { return (
{t( "ui.dev.clients.relationships.title", "Client Relationships", )}
{t( "msg.dev.clients.relationships.subtitle", "RP direct operator relation을 조회하고 User 단위로 추가·삭제합니다.", )}