import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { Save, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; 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 { Textarea } from "../../../components/ui/textarea"; import { toast } from "../../../components/ui/use-toast"; import { approveTenant, deleteTenant, fetchAllTenants, fetchTenant, updateTenant, } from "../../../lib/adminApi"; import { t } from "../../../lib/i18n"; import { DomainTagInput } from "../components/DomainTagInput"; import { ParentTenantSelector } from "../components/ParentTenantSelector"; import { formatDomainConflictMessage, type ServerDomainConflict, } from "../utils/domainTags"; import { mergeTenantOrgConfig, ORG_UNIT_TYPE_OPTIONS, readTenantOrgConfig, removeTenantOrgConfig, shouldAllowHanmacOrgConfig, TENANT_VISIBILITY_OPTIONS, type TenantVisibility, } from "../utils/orgConfig"; import { isSeedTenant } from "../utils/protectedTenants"; export function TenantProfilePage() { const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>(); const tenantId = tenantIdParam ?? ""; const navigate = useNavigate(); const queryClient = useQueryClient(); const tenantQuery = useQuery({ queryKey: ["tenant", tenantId], queryFn: () => fetchTenant(tenantId), enabled: tenantId.length > 0, }); const parentQuery = useQuery({ queryKey: ["tenants", "list-all"], queryFn: () => fetchAllTenants(), }); const [name, setName] = useState(""); const [type, setType] = useState("COMPANY"); const [slug, setSlug] = useState(""); const [description, setDescription] = useState(""); const [status, setStatus] = useState("active"); const [domains, setDomains] = useState([]); const [forceDomainConflicts, setForceDomainConflicts] = useState( [], ); const [parentId, setParentId] = useState(""); const [orgUnitType, setOrgUnitType] = useState(""); const [tenantVisibility, setTenantVisibility] = useState("public"); useEffect(() => { if (tenantQuery.data) { const orgConfig = readTenantOrgConfig(tenantQuery.data.config); setName(tenantQuery.data.name); setType(tenantQuery.data.type || "COMPANY"); setSlug(tenantQuery.data.slug); setDescription(tenantQuery.data.description ?? ""); setStatus(tenantQuery.data.status); setDomains(tenantQuery.data.domains ?? []); setForceDomainConflicts([]); setParentId(tenantQuery.data.parentId ?? ""); setOrgUnitType(orgConfig.orgUnitType); setTenantVisibility(orgConfig.visibility); } }, [tenantQuery.data]); const allTenants = parentQuery.data?.items ?? []; const orgConfigCandidate = tenantQuery.data ? { ...tenantQuery.data, parentId: parentId || undefined, slug, } : undefined; const canEditOrgConfig = orgConfigCandidate ? shouldAllowHanmacOrgConfig(orgConfigCandidate, [ ...allTenants, orgConfigCandidate, ]) : false; const updateMutation = useMutation({ mutationFn: (overrideForceDomains?: string[]) => { const baseConfig = tenantQuery.data?.config; const config = canEditOrgConfig ? mergeTenantOrgConfig(baseConfig, { orgUnitType, visibility: tenantVisibility, }) : removeTenantOrgConfig(baseConfig); return updateTenant(tenantId, { name, type, slug, description: description || undefined, status, parentId: parentId || undefined, domains, forceDomainConflicts: overrideForceDomains ?? forceDomainConflicts, config, }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tenants"] }); queryClient.invalidateQueries({ queryKey: ["tenant", tenantId] }); toast.success(t("msg.info.saved_success", "저장되었습니다.")); }, onError: ( err: AxiosError<{ code?: string; error?: string; conflicts?: ServerDomainConflict[]; }>, ) => { const conflicts = err.response?.data?.conflicts ?? []; if ( err.response?.data?.code === "tenant_domain_conflict" && conflicts.length > 0 ) { const nextForceDomains = Array.from( new Set([...forceDomainConflicts, ...conflicts.map((c) => c.domain)]), ); const message = conflicts.map(formatDomainConflictMessage).join("\n"); if (window.confirm(message)) { setForceDomainConflicts(nextForceDomains); updateMutation.mutate(nextForceDomains); } return; } toast.error( err.response?.data?.error || t("err.common.unknown", "오류가 발생했습니다."), ); }, }); const approveMutation = useMutation({ mutationFn: () => approveTenant(tenantId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tenants"] }); queryClient.invalidateQueries({ queryKey: ["tenant", tenantId] }); toast.success( t("msg.admin.tenants.approve_success", "테넌트가 승인되었습니다."), ); }, onError: (err: AxiosError<{ error?: string }>) => { toast.error( err.response?.data?.error || t("err.common.unknown", "오류가 발생했습니다."), ); }, }); const deleteMutation = useMutation({ mutationFn: () => deleteTenant(tenantId), onSuccess: () => { navigate("/tenants"); toast.success( t("msg.admin.tenants.delete_success", "테넌트가 삭제되었습니다."), ); }, }); const errorMsg = (updateMutation.error as AxiosError<{ error?: string }>) ?.response?.data?.error; const loadError = (tenantQuery.error as AxiosError<{ error?: string }>) ?.response?.data?.error; const isProtectedSeedTenant = tenantQuery.data ? isSeedTenant(tenantQuery.data) : false; if (!tenantId) { return (
{t("msg.admin.tenants.missing_id", "테넌트 ID가 없습니다.")}
); } const handleDelete = () => { if (isProtectedSeedTenant) { return; } if ( window.confirm( t("msg.admin.tenants.delete_confirm", "삭제하시겠습니까?", { name: tenantQuery.data?.name ?? "", }), ) ) { deleteMutation.mutate(); } }; const handleApprove = () => { if ( window.confirm( t("msg.admin.tenants.approve_confirm", "이 테넌트를 승인하시겠습니까?"), ) ) { approveMutation.mutate(); } }; return ( <> {t("ui.admin.tenants.profile.title", "테넌트 프로필")} {t( "ui.admin.tenants.profile.subtitle", "슬러그 및 상태 변경은 즉시 적용됩니다.", )} {loadError && (
{loadError}
)}
setName(e.target.value)} />
{canEditOrgConfig && ( <>
)}
setSlug(e.target.value)} />