From ec90853fe3297a3866b6e9e0090efcfbd2dd2fba Mon Sep 17 00:00:00 2001 From: kyy Date: Wed, 28 Jan 2026 17:48:24 +0900 Subject: [PATCH] =?UTF-8?q?IdP=20=EC=97=B0=EB=8F=99=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=20UI=20=EB=B0=8F=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adminfront/src/app/routes.tsx | 6 +- .../tenants/{ => routes}/TenantCreatePage.tsx | 14 +-- .../tenants/routes/TenantDetailPage.tsx | 71 ++++++++++++++ .../tenants/routes/TenantFederationPage.tsx | 94 +++++++++++++++++++ .../tenants/{ => routes}/TenantListPage.tsx | 10 +- .../TenantProfilePage.tsx} | 86 +++++++---------- 6 files changed, 215 insertions(+), 66 deletions(-) rename adminfront/src/features/tenants/{ => routes}/TenantCreatePage.tsx (92%) create mode 100644 adminfront/src/features/tenants/routes/TenantDetailPage.tsx create mode 100644 adminfront/src/features/tenants/routes/TenantFederationPage.tsx rename adminfront/src/features/tenants/{ => routes}/TenantListPage.tsx (95%) rename adminfront/src/features/tenants/{TenantDetailPage.tsx => routes/TenantProfilePage.tsx} (65%) diff --git a/adminfront/src/app/routes.tsx b/adminfront/src/app/routes.tsx index 262320a1..df1d0c21 100644 --- a/adminfront/src/app/routes.tsx +++ b/adminfront/src/app/routes.tsx @@ -6,9 +6,9 @@ import AuditLogsPage from "../features/audit/AuditLogsPage"; import AuthPage from "../features/auth/AuthPage"; import DashboardPage from "../features/dashboard/DashboardPage"; import GlobalOverviewPage from "../features/overview/GlobalOverviewPage"; -import TenantCreatePage from "../features/tenants/TenantCreatePage"; -import TenantDetailPage from "../features/tenants/TenantDetailPage"; -import TenantListPage from "../features/tenants/TenantListPage"; +import TenantCreatePage from "../features/tenants/routes/TenantCreatePage"; +import TenantDetailPage from "../features/tenants/routes/TenantDetailPage"; +import TenantListPage from "../features/tenants/routes/TenantListPage"; import UserCreatePage from "../features/users/UserCreatePage"; import UserDetailPage from "../features/users/UserDetailPage"; import UserListPage from "../features/users/UserListPage"; diff --git a/adminfront/src/features/tenants/TenantCreatePage.tsx b/adminfront/src/features/tenants/routes/TenantCreatePage.tsx similarity index 92% rename from adminfront/src/features/tenants/TenantCreatePage.tsx rename to adminfront/src/features/tenants/routes/TenantCreatePage.tsx index f48b67e4..6d67d81d 100644 --- a/adminfront/src/features/tenants/TenantCreatePage.tsx +++ b/adminfront/src/features/tenants/routes/TenantCreatePage.tsx @@ -3,19 +3,19 @@ import type { AxiosError } from "axios"; import { Building2, Sparkles } from "lucide-react"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; -import { Badge } from "../../components/ui/badge"; -import { Button } from "../../components/ui/button"; +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 { Textarea } from "../../components/ui/textarea"; -import { createTenant } from "../../lib/adminApi"; +} from "../../../components/ui/card"; +import { Input } from "../../../components/ui/input"; +import { Label } from "../../../components/ui/label"; +import { Textarea } from "../../../components/ui/textarea"; +import { createTenant } from "../../../lib/adminApi"; function TenantCreatePage() { const navigate = useNavigate(); diff --git a/adminfront/src/features/tenants/routes/TenantDetailPage.tsx b/adminfront/src/features/tenants/routes/TenantDetailPage.tsx new file mode 100644 index 00000000..14080889 --- /dev/null +++ b/adminfront/src/features/tenants/routes/TenantDetailPage.tsx @@ -0,0 +1,71 @@ +import { useQuery } from "@tanstack/react-query"; +import { ArrowLeft } from "lucide-react"; +import { Link, Outlet, useLocation, useParams } from "react-router-dom"; +import { Badge } from "../../../components/ui/badge"; +import { fetchTenant } from "../../../lib/adminApi"; + +function TenantDetailPage() { + const { tenantId } = useParams<{ tenantId: string }>(); + const location = useLocation(); + + const tenantQuery = useQuery({ + queryKey: ["tenant", tenantId], + queryFn: () => fetchTenant(tenantId!), + enabled: !!tenantId, + }); + + const isFederationTab = location.pathname.includes("/federation"); + + return ( +
+
+
+
+ + + Tenants + + / + Detail +
+

+ {tenantQuery.data?.name ?? "Loading Tenant..."} +

+

+ Edit tenant information or manage federation settings. +

+
+ Admin only +
+ + {/* Tabs */} +
+ + Profile + + + Federation + +
+ + {/* Outlet for nested routes */} + +
+ ); +} + +export default TenantDetailPage; diff --git a/adminfront/src/features/tenants/routes/TenantFederationPage.tsx b/adminfront/src/features/tenants/routes/TenantFederationPage.tsx new file mode 100644 index 00000000..e3c08d62 --- /dev/null +++ b/adminfront/src/features/tenants/routes/TenantFederationPage.tsx @@ -0,0 +1,94 @@ +import { useParams } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; +import { listIdpConfigsForTenant } from "../../../lib/adminApi"; + +export function TenantFederationPage() { + const { tenantId } = useParams<{ tenantId: string }>(); + + if (!tenantId) { + return
Tenant ID is missing
; + } + + const { data, isLoading, error } = useQuery({ + queryKey: ["idpConfigs", tenantId], + queryFn: () => listIdpConfigsForTenant(tenantId), + }); + + return ( +
+

+ Identity Federation Settings +

+

+ Manage external identity providers for this tenant. +

+ +
+ +
+ + {isLoading &&
Loading configurations...
} + {error && ( +
+ Failed to load configurations: {error.message} +
+ )} + + {data && ( +
+ + + + + + + + + + + {data.length === 0 ? ( + + + + ) : ( + data.map((config) => ( + + + + + + + )) + )} + +
Display NameProvider TypeStatusActions
+ No IdP configurations found. +
+ {config.display_name} + + {config.provider_type.toUpperCase()} + + + {config.status} + + + + +
+
+ )} +
+ ); +} diff --git a/adminfront/src/features/tenants/TenantListPage.tsx b/adminfront/src/features/tenants/routes/TenantListPage.tsx similarity index 95% rename from adminfront/src/features/tenants/TenantListPage.tsx rename to adminfront/src/features/tenants/routes/TenantListPage.tsx index 04c3c95c..92438a11 100644 --- a/adminfront/src/features/tenants/TenantListPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantListPage.tsx @@ -2,15 +2,15 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { Pencil, Plus, RefreshCw, Trash2 } from "lucide-react"; import { Link, useNavigate } from "react-router-dom"; -import { Badge } from "../../components/ui/badge"; -import { Button } from "../../components/ui/button"; +import { Badge } from "../../../components/ui/badge"; +import { Button } from "../../../components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, -} from "../../components/ui/card"; +} from "../../../components/ui/card"; import { Table, TableBody, @@ -18,8 +18,8 @@ import { TableHead, TableHeader, TableRow, -} from "../../components/ui/table"; -import { deleteTenant, fetchTenants } from "../../lib/adminApi"; +} from "../../../components/ui/table"; +import { deleteTenant, fetchTenants } from "../../../lib/adminApi"; function TenantListPage() { const navigate = useNavigate(); diff --git a/adminfront/src/features/tenants/TenantDetailPage.tsx b/adminfront/src/features/tenants/routes/TenantProfilePage.tsx similarity index 65% rename from adminfront/src/features/tenants/TenantDetailPage.tsx rename to adminfront/src/features/tenants/routes/TenantProfilePage.tsx index 1e85864b..44c0aa9a 100644 --- a/adminfront/src/features/tenants/TenantDetailPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantProfilePage.tsx @@ -1,31 +1,36 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import type { AxiosError } from "axios"; -import { ArrowLeft, Save, Trash2 } from "lucide-react"; +import { Save, Trash2 } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; -import { Link, useNavigate, useParams } from "react-router-dom"; -import { Badge } from "../../components/ui/badge"; -import { Button } from "../../components/ui/button"; +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 { deleteTenant, fetchTenant, updateTenant } from "../../lib/adminApi"; +} from "../../../components/ui/card"; +import { Input } from "../../../components/ui/input"; +import { Label } from "../../../components/ui/label"; +import { Textarea } from "../../../components/ui/textarea"; +import { + deleteTenant, + fetchTenant, + updateTenant, +} from "../../../lib/adminApi"; -function TenantDetailPage() { - const { id } = useParams(); +export function TenantProfilePage() { + const { tenantId } = useParams<{ tenantId: string }>(); const navigate = useNavigate(); - const tenantId = useMemo(() => id ?? "", [id]); + + if (!tenantId) { + return
Tenant ID is missing
; + } const tenantQuery = useQuery({ queryKey: ["tenant", tenantId], queryFn: () => fetchTenant(tenantId), - enabled: tenantId !== "", }); const [name, setName] = useState(""); @@ -34,13 +39,12 @@ function TenantDetailPage() { const [status, setStatus] = useState("active"); useEffect(() => { - if (!tenantQuery.data) { - return; + if (tenantQuery.data) { + setName(tenantQuery.data.name); + setSlug(tenantQuery.data.slug); + setDescription(tenantQuery.data.description ?? ""); + setStatus(tenantQuery.data.status); } - setName(tenantQuery.data.name); - setSlug(tenantQuery.data.slug); - setDescription(tenantQuery.data.description ?? ""); - setStatus(tenantQuery.data.status); }, [tenantQuery.data]); const updateMutation = useMutation({ @@ -69,36 +73,19 @@ function TenantDetailPage() { ?.response?.data?.error; const handleDelete = () => { - if (!window.confirm("이 테넌트를 삭제할까요?")) { - return; + if (window.confirm("Are you sure you want to delete this tenant?")) { + deleteMutation.mutate(); } - deleteMutation.mutate(); }; return ( -
-
-
-
- - - Tenants - - / - Detail -
-

테넌트 상세

-

- 테넌트 정보를 수정하거나 삭제할 수 있습니다. -

-
- Admin only -
- - + <> + Tenant profile - Slug와 상태 변경은 바로 적용됩니다. + + Changes to slug and status are applied immediately. + {loadError && ( @@ -143,7 +130,6 @@ function TenantDetailPage() {
- {errorMsg && (
{errorMsg} @@ -152,18 +138,18 @@ function TenantDetailPage() { -
+
-
+ ); } - -export default TenantDetailPage;