1
0
forked from baron/baron-sso

앱 생성 개발자 권한 신청 안내 추가

This commit is contained in:
2026-05-20 13:21:37 +09:00
parent dcb442b68d
commit b55ab7bc67
6 changed files with 166 additions and 10 deletions

View File

@@ -60,6 +60,7 @@ import { t } from "../../lib/i18n";
import { resolveProfileRole } from "../../lib/role";
import { cn } from "../../lib/utils";
import { fetchMe } from "../auth/authApi";
import { resolveClientCreateAccess } from "./clientCreateAccess";
import { ClientLogo } from "./components/ClientLogo";
type ClientSortKey = "application" | "id" | "type" | "status" | "createdAt";
@@ -96,7 +97,8 @@ function ClientsPage() {
} = useQuery({
queryKey: ["developer-request", tenantId],
queryFn: () => fetchDeveloperRequestStatus(tenantId),
enabled: hasAccessToken && role === "user",
enabled:
hasAccessToken && (role === "user" || role === "tenant_member"),
});
const { data: tenants } = useQuery({
queryKey: ["myTenants"],
@@ -109,15 +111,14 @@ function ClientsPage() {
enabled: hasAccessToken,
});
const canCreateClient =
(role !== "user" && role !== "tenant_member") ||
requestStatus?.status === "approved";
const isDeveloperRequestPending = requestStatus?.status === "pending";
const createAccessState = resolveClientCreateAccess({
role,
requestStatus: requestStatus?.status,
});
const canCreateClient = createAccessState === "can_create";
const isDeveloperRequestPending = createAccessState === "pending";
const canRequestDeveloperAccess =
role === "user" &&
!isLoadingRequest &&
!canCreateClient &&
!isDeveloperRequestPending;
createAccessState === "request_required" && !isLoadingRequest;
const [searchQuery, setSearchQuery] = useState("");
const [typeFilter, setTypeFilter] = useState("all");
@@ -278,7 +279,54 @@ function ClientsPage() {
<Plus className="h-4 w-4" />
{t("ui.dev.clients.new", "새 클라이언트")}
</Button>
) : null
) : isDeveloperRequestPending ? (
<div className="flex items-center justify-end gap-3">
<p className="max-w-xs text-right text-sm text-muted-foreground">
{t(
"msg.dev.clients.create_pending_detail",
"개발자 권한 신청을 검토 중입니다. 승인되면 연동 앱을 추가할 수 있습니다.",
)}
</p>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => navigate("/developer-requests")}
>
{t("ui.dev.nav.developer_request", "개발자 권한 신청")}
</Button>
</div>
) : canRequestDeveloperAccess ? (
<div className="flex items-center justify-end gap-3">
<p className="max-w-xs whitespace-pre-line text-right text-sm text-muted-foreground">
{t(
"msg.dev.clients.create_requires_request",
"연동 앱을 생성할 권한이 없습니다.\n개발자 권한 신청을 요청한 뒤 승인 받아주세요.",
).replaceAll("\\n", "\n")}
</p>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => navigate("/developer-requests")}
>
{t("ui.dev.welcome.btn_request", "개발자 권한 신청")}
</Button>
</div>
) : (
<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_forbidden_detail",
"연동 앱을 생성할 권한이 없습니다. 관리자에게 개발자 권한 또는 적절한 RP 권한 부여를 요청해 주세요.",
)}
</p>
<Button type="button" variant="outline" size="sm" disabled>
<Plus className="h-4 w-4" />
{t("ui.dev.clients.new", "새 클라이언트")}
</Button>
</div>
)
}
/>

View File

@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import { resolveClientCreateAccess } from "./clientCreateAccess";
describe("client create access", () => {
it("allows privileged roles to create clients without developer request approval", () => {
expect(
resolveClientCreateAccess({
role: "rp_admin",
}),
).toBe("can_create");
});
it("requires a developer request for basic users without approval", () => {
expect(
resolveClientCreateAccess({
role: "user",
requestStatus: "none",
}),
).toBe("request_required");
});
it("shows pending state while a developer request is under review", () => {
expect(
resolveClientCreateAccess({
role: "tenant_member",
requestStatus: "pending",
}),
).toBe("pending");
});
it("allows client creation after developer request approval", () => {
expect(
resolveClientCreateAccess({
role: "user",
requestStatus: "approved",
}),
).toBe("can_create");
});
it("routes cancelled or rejected requests back to requestable state", () => {
expect(
resolveClientCreateAccess({
role: "user",
requestStatus: "cancelled",
}),
).toBe("request_required");
expect(
resolveClientCreateAccess({
role: "user",
requestStatus: "rejected",
}),
).toBe("request_required");
});
});

View File

@@ -0,0 +1,44 @@
import type { DeveloperRequestStatus } from "../../lib/devApi";
export type ClientCreateAccessState =
| "can_create"
| "pending"
| "request_required"
| "forbidden";
type ResolveClientCreateAccessParams = {
role: string;
requestStatus?: DeveloperRequestStatus;
};
function canSelfRequestDeveloperAccess(role: string) {
return role === "user" || role === "tenant_member";
}
export function resolveClientCreateAccess({
role,
requestStatus,
}: ResolveClientCreateAccessParams): ClientCreateAccessState {
if (!canSelfRequestDeveloperAccess(role)) {
return "can_create";
}
if (requestStatus === "approved") {
return "can_create";
}
if (requestStatus === "pending") {
return "pending";
}
if (
requestStatus === "none" ||
requestStatus === "rejected" ||
requestStatus === "cancelled" ||
typeof requestStatus === "undefined"
) {
return "request_required";
}
return "forbidden";
}

View File

@@ -341,6 +341,9 @@ empty = "No RPs are available."
empty_detail = "RPs will appear here when a relationship is assigned to your account."
empty_can_create = "No linked apps have been registered yet."
empty_can_create_detail = "Create a new RP with the Add linked app button, and it will appear here."
create_requires_request = "You do not have permission to create applications.\nSubmit a developer access request and wait for approval."
create_pending_detail = "Your developer access request is under review. You will be able to add applications after approval."
create_forbidden_detail = "You do not have permission to create applications. Ask an administrator to grant developer access or the appropriate RP permissions."
empty_filtered = "No linked apps match the current filters."
empty_filtered_detail = "Try changing the search text or filters."
empty_pending = "Your developer access request is under review."

View File

@@ -338,6 +338,9 @@ empty = "조회 가능한 RP가 없습니다."
empty_detail = "RP 관계가 부여되면 이 목록에 해당 RP가 표시됩니다."
empty_can_create = "아직 등록된 연동 앱이 없습니다."
empty_can_create_detail = "연동 앱 추가 버튼으로 새 RP를 생성하면 이 목록에 표시됩니다."
create_requires_request = "연동 앱을 생성할 권한이 없습니다.\n개발자 권한 신청을 요청한 뒤 승인 받아주세요."
create_pending_detail = "개발자 권한 신청을 검토 중입니다. 승인되면 연동 앱을 추가할 수 있습니다."
create_forbidden_detail = "연동 앱을 생성할 권한이 없습니다. 관리자에게 개발자 권한 또는 적절한 RP 권한 부여를 요청해 주세요."
empty_filtered = "조건에 맞는 연동 앱이 없습니다."
empty_filtered_detail = "검색어나 필터 조건을 변경해 보세요."
empty_pending = "개발자 권한 신청을 검토 중입니다."

View File

@@ -379,6 +379,9 @@ empty = ""
empty_detail = ""
empty_can_create = ""
empty_can_create_detail = ""
create_requires_request = ""
create_pending_detail = ""
create_forbidden_detail = ""
empty_filtered = ""
empty_filtered_detail = ""
empty_pending = ""