From 8d0982b89c482bfca2f8ee0f8063e8f284ff1ad2 Mon Sep 17 00:00:00 2001 From: kyy Date: Wed, 15 Apr 2026 17:18:04 +0900 Subject: [PATCH] =?UTF-8?q?devfront=20RP=20=EC=83=81=EC=84=B8=20=ED=83=AD?= =?UTF-8?q?=20i18n=20=EB=B0=8F=20=EC=88=9C=EC=84=9C=20=EC=9D=BC=EA=B4=80?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/clients/ClientConsentsPage.tsx | 25 +------ .../src/features/clients/ClientDetailTabs.tsx | 54 +++++++++++++++ .../features/clients/ClientDetailsPage.tsx | 28 +------- .../features/clients/ClientGeneralPage.tsx | 31 ++------- .../features/clients/ClientRelationsPage.tsx | 43 +++--------- devfront/src/locales/en.toml | 1 + devfront/src/locales/ko.toml | 1 + devfront/src/locales/template.toml | 1 + devfront/tests/devfront-client-tabs.spec.ts | 68 +++++++++++++++++++ 9 files changed, 144 insertions(+), 108 deletions(-) create mode 100644 devfront/src/features/clients/ClientDetailTabs.tsx create mode 100644 devfront/tests/devfront-client-tabs.spec.ts diff --git a/devfront/src/features/clients/ClientConsentsPage.tsx b/devfront/src/features/clients/ClientConsentsPage.tsx index de941fd7..c2498d7d 100644 --- a/devfront/src/features/clients/ClientConsentsPage.tsx +++ b/devfront/src/features/clients/ClientConsentsPage.tsx @@ -29,6 +29,7 @@ import { import { fetchClient, fetchConsents, revokeConsent } from "../../lib/devApi"; import { t } from "../../lib/i18n"; import { cn } from "../../lib/utils"; +import { ClientDetailTabs } from "./ClientDetailTabs"; function ClientConsentsPage() { const params = useParams(); @@ -214,29 +215,7 @@ function ClientConsentsPage() { -
- - {t("ui.dev.clients.details.tab.connection", "Federation")} - - - {t("ui.dev.clients.details.tab.consents", "Consent & Users")} - - - {t("ui.dev.clients.details.tab.settings", "Settings")} - - - {t("ui.dev.clients.details.tab.relationships", "Relationships")} - -
+ diff --git a/devfront/src/features/clients/ClientDetailTabs.tsx b/devfront/src/features/clients/ClientDetailTabs.tsx new file mode 100644 index 00000000..8b5372c1 --- /dev/null +++ b/devfront/src/features/clients/ClientDetailTabs.tsx @@ -0,0 +1,54 @@ +import { Link } from "react-router-dom"; +import { t } from "../../lib/i18n"; +import { cn } from "../../lib/utils"; + +type ClientDetailTab = "connection" | "consents" | "settings" | "relationships"; + +interface ClientDetailTabsProps { + activeTab: ClientDetailTab; + clientId: string; +} + +const tabOrder: Array<{ + key: ClientDetailTab; + href: (clientId: string) => string; +}> = [ + { key: "connection", href: (clientId) => `/clients/${clientId}` }, + { key: "consents", href: (clientId) => `/clients/${clientId}/consents` }, + { key: "settings", href: (clientId) => `/clients/${clientId}/settings` }, + { + key: "relationships", + href: (clientId) => `/clients/${clientId}/relationships`, + }, +]; + +export function ClientDetailTabs({ + activeTab, + clientId, +}: ClientDetailTabsProps) { + return ( +
+ {tabOrder.map((tab) => { + const isActive = tab.key === activeTab; + return isActive ? ( + + {t(`ui.dev.clients.details.tab.${tab.key}`)} + + ) : ( + + {t(`ui.dev.clients.details.tab.${tab.key}`)} + + ); + })} +
+ ); +} diff --git a/devfront/src/features/clients/ClientDetailsPage.tsx b/devfront/src/features/clients/ClientDetailsPage.tsx index eabdfc1c..7d6825fb 100644 --- a/devfront/src/features/clients/ClientDetailsPage.tsx +++ b/devfront/src/features/clients/ClientDetailsPage.tsx @@ -38,6 +38,7 @@ import { } from "../../lib/devApi"; import { t } from "../../lib/i18n"; import { cn } from "../../lib/utils"; +import { ClientDetailTabs } from "./ClientDetailTabs"; function ClientDetailsPage() { const params = useParams(); @@ -253,32 +254,7 @@ function ClientDetailsPage() { : t("msg.common.loading", "Loading...")} -
- - {t("ui.dev.clients.details.tab.connection", "Federation")} - - - {t("ui.dev.clients.details.tab.consents", "Consent & Users")} - - - {t("ui.dev.clients.details.tab.settings", "Settings")} - - - {t("ui.dev.clients.details.tab.relationships", "Relationships")} - -
+
diff --git a/devfront/src/features/clients/ClientGeneralPage.tsx b/devfront/src/features/clients/ClientGeneralPage.tsx index 2da52255..dedff139 100644 --- a/devfront/src/features/clients/ClientGeneralPage.tsx +++ b/devfront/src/features/clients/ClientGeneralPage.tsx @@ -43,6 +43,7 @@ import type { } from "../../lib/devApi"; import { t } from "../../lib/i18n"; import { cn } from "../../lib/utils"; +import { ClientDetailTabs } from "./ClientDetailTabs"; interface ScopeItem { id: string; @@ -665,33 +666,9 @@ function ClientGeneralPage() { )}
-
- {!isCreate && ( - <> - - {t("ui.dev.clients.details.tab.connection", "Federation")} - - - {t("ui.dev.clients.details.tab.consents", "Consent & Users")} - - - {t("ui.dev.clients.details.tab.relationships", "Relationships")} - - - {t("ui.dev.clients.details.tab.settings", "Settings")} - - - )} -
+ {!isCreate && ( + + )} {/* 1. Application Identity */} diff --git a/devfront/src/features/clients/ClientRelationsPage.tsx b/devfront/src/features/clients/ClientRelationsPage.tsx index bb0ee916..af1120f7 100644 --- a/devfront/src/features/clients/ClientRelationsPage.tsx +++ b/devfront/src/features/clients/ClientRelationsPage.tsx @@ -30,6 +30,7 @@ import { removeClientRelation, } from "../../lib/devApi"; import { t } from "../../lib/i18n"; +import { ClientDetailTabs } from "./ClientDetailTabs"; const relationOptions = [ "admins", @@ -48,9 +49,8 @@ function ClientRelationsPage() { const params = useParams(); const queryClient = useQueryClient(); const clientId = params.id ?? ""; - const [relation, setRelation] = useState<(typeof relationOptions)[number]>( - "config_editor", - ); + const [relation, setRelation] = + useState<(typeof relationOptions)[number]>("config_editor"); const [userId, setUserId] = useState(""); const { data: clientData } = useQuery({ @@ -86,7 +86,9 @@ function ClientRelationsPage() { userId: userId.trim(), }), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["client-relations", clientId] }); + queryClient.invalidateQueries({ + queryKey: ["client-relations", clientId], + }); setUserId(""); toast( t( @@ -115,7 +117,9 @@ function ClientRelationsPage() { mutationFn: (payload: { relation: string; subject: string }) => removeClientRelation(clientId, payload.relation, payload.subject), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["client-relations", clientId] }); + queryClient.invalidateQueries({ + queryKey: ["client-relations", clientId], + }); toast( t( "msg.dev.clients.relationships.removed", @@ -191,10 +195,7 @@ function ClientRelationsPage() { {clientData?.client?.name || clientId} / - {t( - "ui.dev.clients.details.tab.relationships", - "Relationships", - )} + {t("ui.dev.clients.details.tab.relationships", "Relationships")}
@@ -231,29 +232,7 @@ function ClientRelationsPage() {
-
- - {t("ui.dev.clients.details.tab.connection", "Federation")} - - - {t("ui.dev.clients.details.tab.consents", "Consent & Users")} - - - {t("ui.dev.clients.details.tab.settings", "Settings")} - - - {t("ui.dev.clients.details.tab.relationships", "Relationships")} - -
+ diff --git a/devfront/src/locales/en.toml b/devfront/src/locales/en.toml index 87256821..e9e51705 100644 --- a/devfront/src/locales/en.toml +++ b/devfront/src/locales/en.toml @@ -1363,6 +1363,7 @@ title = "Security Note" connection = "Federation" consents = "Consent & Users" settings = "Settings" +relationships = "Relationships" [ui.dev.clients.general] create = "Create Application" diff --git a/devfront/src/locales/ko.toml b/devfront/src/locales/ko.toml index 2ac3c74a..00ec0f3d 100644 --- a/devfront/src/locales/ko.toml +++ b/devfront/src/locales/ko.toml @@ -1362,6 +1362,7 @@ title = "보안 메모" connection = "연동 설정" consents = "동의 및 사용자" settings = "설정" +relationships = "관계" [ui.dev.clients.general] create = "앱 생성" diff --git a/devfront/src/locales/template.toml b/devfront/src/locales/template.toml index 7bff5ee6..d6252524 100644 --- a/devfront/src/locales/template.toml +++ b/devfront/src/locales/template.toml @@ -1363,6 +1363,7 @@ title = "" connection = "" consents = "" settings = "" +relationships = "" [ui.dev.clients.general] create = "" diff --git a/devfront/tests/devfront-client-tabs.spec.ts b/devfront/tests/devfront-client-tabs.spec.ts new file mode 100644 index 00000000..69d0b5a0 --- /dev/null +++ b/devfront/tests/devfront-client-tabs.spec.ts @@ -0,0 +1,68 @@ +import { type Page, expect, test } from "@playwright/test"; +import { + type ClientRelation, + type Consent, + installDevApiMock, + makeClient, + seedAuth, +} from "./helpers/devfront-fixtures"; + +function expectClientTabsOrder(pagePath: string, expectedActive: RegExp) { + return async ({ page }: { page: Page }) => { + const state = { + clients: [makeClient("client-tabs", { name: "탭 테스트 앱" })], + consents: [] as Consent[], + relations: { + "client-tabs": [ + { + relation: "config_editor", + subject: "User:user-1", + subjectType: "User", + subjectId: "user-1", + }, + ] satisfies ClientRelation[], + }, + auditLogsByCursor: undefined, + }; + await installDevApiMock(page, state); + + await page.goto(pagePath); + + const header = page + .locator("header") + .filter({ hasText: "탭 테스트 앱" }) + .first(); + const tabs = header.locator( + "div.border-b.border-border .whitespace-nowrap", + ); + + await expect(tabs).toHaveText([ + "연동 설정", + "동의 및 사용자", + "설정", + "관계", + ]); + + await expect( + header + .locator("div.border-b.border-border .text-primary") + .filter({ hasText: expectedActive }), + ).toHaveCount(1); + }; +} + +test.describe("DevFront client detail tabs", () => { + test.beforeEach(async ({ page }) => { + await seedAuth(page, "rp_admin"); + }); + + test( + "settings page keeps tab order and uses localized relationships label", + expectClientTabsOrder("/clients/client-tabs/settings", /^설정$/), + ); + + test( + "relationships page keeps tab order and uses localized relationships label", + expectClientTabsOrder("/clients/client-tabs/relationships", /^관계$/), + ); +});