1
0
forked from baron/baron-sso

devfront 테넌트 미소속 개발자 신청 안내 추가

This commit is contained in:
2026-06-08 13:52:40 +09:00
parent 894feb20f1
commit 41e755b1c7
11 changed files with 228 additions and 22 deletions

View File

@@ -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();
});
});

View File

@@ -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 ? (
<div className="flex flex-col items-end gap-2 text-right">
<p className="max-w-xs text-sm text-muted-foreground">
{t(
"msg.dev.clients.create_requires_tenant",
"개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.",
)}
</p>
<Button type="button" variant="outline" size="sm" disabled>
<Plus className="h-4 w-4" />
{t("ui.dev.welcome.btn_request", "개발자 권한 신청")}
</Button>
</div>
) : canCreateClient ? (
<Button
size="sm"
className="mt-1 shadow-lg shadow-primary/30"
@@ -453,6 +470,11 @@ function ClientsPage() {
"msg.dev.clients.empty_filtered",
"조건에 맞는 연동 앱이 없습니다.",
)
: isTenantContextMissing
? t(
"msg.dev.clients.empty_tenant_missing",
"개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.",
)
: canCreateClient
? t(
"msg.dev.clients.empty_can_create",
@@ -475,6 +497,11 @@ function ClientsPage() {
"msg.dev.clients.empty_filtered_detail",
"검색어나 필터 조건을 변경해 보세요.",
)
: isTenantContextMissing
? t(
"msg.dev.clients.empty_tenant_missing_detail",
"현재 계정은 테넌트와 연결되어 있지 않아 개발자 권한을 신청할 수 없습니다.",
)
: canCreateClient
? t(
"msg.dev.clients.empty_can_create_detail",
@@ -499,6 +526,18 @@ function ClientsPage() {
{t("ui.dev.clients.new", "연동 앱 추가")}
</button>
)}
{!isFilteredOut && isTenantContextMissing && (
<button
type="button"
className="font-bold text-muted-foreground"
disabled
>
{t(
"ui.dev.welcome.btn_request",
"개발자 등록 신청하기",
)}
</button>
)}
{!isFilteredOut && canRequestDeveloperAccess && (
<button
type="button"
@@ -672,6 +711,7 @@ function RequestAccessModal({
const [name, setName] = useState(initialName);
const [organization, setOrganization] = useState(initialOrg);
const [reason, setReason] = useState("");
const isTenantContextMissing = !tenantId.trim();
useEffect(() => {
if (!isOpen) return;
@@ -688,6 +728,15 @@ function RequestAccessModal({
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (isTenantContextMissing) {
alert(
t(
"msg.dev.clients.create_requires_tenant",
"개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.",
),
);
return;
}
mutation.mutate({
name,
organization,