diff --git a/devfront/src/features/audit/AuditLogsPage.test.tsx b/devfront/src/features/audit/AuditLogsPage.test.tsx index a8b9f9db..284094d2 100644 --- a/devfront/src/features/audit/AuditLogsPage.test.tsx +++ b/devfront/src/features/audit/AuditLogsPage.test.tsx @@ -174,6 +174,22 @@ describe("AuditLogsPage", () => { expect(navigateMock).toHaveBeenCalledWith("/developer-requests"); }); + it("shows a tenant-required notice when tenant context is missing", async () => { + gateState = { + hasDeveloperAccess: false, + isDeveloperRequestPending: false, + canRequestDeveloperAccess: false, + isLoadingDeveloperAccessGate: false, + isTenantContextMissing: true, + }; + + const container = await renderPage(); + expect(container.textContent).toContain( + "개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.", + ); + expect(container.textContent).not.toContain("개발자 권한 신청"); + }); + it("exports the fetched logs as CSV", async () => { const createObjectURL = vi .spyOn(URL, "createObjectURL") diff --git a/devfront/src/features/audit/AuditLogsPage.tsx b/devfront/src/features/audit/AuditLogsPage.tsx index f7d939bb..2d665ff5 100644 --- a/devfront/src/features/audit/AuditLogsPage.tsx +++ b/devfront/src/features/audit/AuditLogsPage.tsx @@ -97,6 +97,7 @@ function AuditLogsPage() { isDeveloperRequestPending, canRequestDeveloperAccess, isLoadingDeveloperAccessGate, + isTenantContextMissing, } = useDeveloperAccessGate({ hasAccessToken, profileRole, @@ -142,6 +143,24 @@ function AuditLogsPage() { } if (!hasDeveloperAccess) { + const deniedMessage = isTenantContextMissing + ? t( + "msg.dev.request.tenant_required", + "개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.", + ) + : t( + "msg.dev.audit.access_denied", + "감사 로그는 개발자 권한이 있어야 볼 수 있습니다.", + ); + const deniedDetailMessage = isTenantContextMissing + ? t( + "msg.dev.request.tenant_required_detail", + "현재 계정은 테넌트와 연결되어 있지 않아 개발자 권한을 신청할 수 없습니다.", + ) + : t( + "msg.dev.audit.access_denied_detail", + "개발자 권한 신청 페이지에서 신청을 등록한 뒤 승인을 받아주세요.", + ); return ( navigate("/developer-requests")} /> diff --git a/devfront/src/features/clients/ClientsPage.test.tsx b/devfront/src/features/clients/ClientsPage.test.tsx index 2f92ecba..6a958ff6 100644 --- a/devfront/src/features/clients/ClientsPage.test.tsx +++ b/devfront/src/features/clients/ClientsPage.test.tsx @@ -277,4 +277,31 @@ describe("ClientsPage", () => { expect(navigateMock).toHaveBeenCalledWith("/developer-requests"); }); + + it("shows a tenant-required notice when tenant context is missing", async () => { + authState = { + user: { + access_token: "access-token", + profile: { + role: "user", + companyCode: "HANMAC", + name: "Requester", + email: "requester@example.com", + phone: "010-1234-5678", + }, + }, + }; + fetchMeMock.mockResolvedValue({ + role: "user", + name: "Requester", + email: "requester@example.com", + phone: "010-1234-5678", + }); + + const container = await renderPage(); + expect(container.textContent).toContain( + "개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.", + ); + expect(fetchDeveloperRequestStatusMock).not.toHaveBeenCalled(); + }); }); diff --git a/devfront/src/features/clients/ClientsPage.tsx b/devfront/src/features/clients/ClientsPage.tsx index 6c22bc88..e82b2c5e 100644 --- a/devfront/src/features/clients/ClientsPage.tsx +++ b/devfront/src/features/clients/ClientsPage.tsx @@ -67,6 +67,7 @@ function ClientsPage() { const role = resolveProfileRole(userProfile); const tenantId = userProfile?.tenant_id as string | undefined; const companyCode = userProfile?.companyCode as string | undefined; + const isTenantContextMissing = !tenantId?.trim(); const { data, @@ -93,7 +94,8 @@ function ClientsPage() { } = useQuery({ queryKey: ["developer-request", tenantId], queryFn: () => fetchDeveloperRequestStatus(tenantId), - enabled: hasAccessToken && profileRole === "user", + enabled: + hasAccessToken && profileRole === "user" && !isTenantContextMissing, }); const { data: tenants } = useQuery({ queryKey: ["myTenants"], @@ -108,7 +110,9 @@ function ClientsPage() { const canCreateClient = createAccessState === "can_create"; const isDeveloperRequestPending = createAccessState === "pending"; const canRequestDeveloperAccess = - createAccessState === "request_required" && !isLoadingRequest; + createAccessState === "request_required" && + !isLoadingRequest && + !isTenantContextMissing; const [searchQuery, setSearchQuery] = useState(""); const [typeFilter, setTypeFilter] = useState("all"); @@ -229,7 +233,20 @@ function ClientsPage() { "OIDC 클라이언트, 인증 방식, 리다이렉트 URI, 비밀키 재발행을 감사 로그와 함께 관리합니다.", )} actions={ - canCreateClient ? ( + isTenantContextMissing ? ( +
+

+ {t( + "msg.dev.clients.create_requires_tenant", + "개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.", + )} +

+ +
+ ) : canCreateClient ? ( + )} {!isFilteredOut && canRequestDeveloperAccess && (