forked from baron/baron-sso
개요/감사로그 CTA 공통화
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
import { act } from "react-dom/test-utils";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { DeveloperAccessRequestCard } from "./DeveloperAccessRequestCard";
|
||||||
|
|
||||||
|
describe("DeveloperAccessRequestCard", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.innerHTML = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the request CTA for pending and denied states", () => {
|
||||||
|
const onAction = vi.fn();
|
||||||
|
const container = document.createElement("div");
|
||||||
|
document.body.appendChild(container);
|
||||||
|
const root = createRoot(container);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.render(
|
||||||
|
<DeveloperAccessRequestCard
|
||||||
|
title="운영 현황"
|
||||||
|
isPending={true}
|
||||||
|
canRequest={false}
|
||||||
|
pendingMessage="검토 중"
|
||||||
|
deniedMessage="거부됨"
|
||||||
|
pendingDetailMessage="승인 대기"
|
||||||
|
deniedDetailMessage="신청 필요"
|
||||||
|
actionLabel="개발자 권한 신청"
|
||||||
|
onAction={onAction}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector("h2")?.textContent).toBe("운영 현황");
|
||||||
|
expect(container.textContent).toContain("검토 중");
|
||||||
|
expect(container.textContent).toContain("승인 대기");
|
||||||
|
|
||||||
|
const button = container.querySelector("button");
|
||||||
|
expect(button?.textContent).toBe("개발자 권한 신청");
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
});
|
||||||
|
expect(onAction).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
root.render(
|
||||||
|
<DeveloperAccessRequestCard
|
||||||
|
title="감사 로그"
|
||||||
|
isPending={false}
|
||||||
|
canRequest={true}
|
||||||
|
pendingMessage="검토 중"
|
||||||
|
deniedMessage="거부됨"
|
||||||
|
pendingDetailMessage="승인 대기"
|
||||||
|
deniedDetailMessage="신청 필요"
|
||||||
|
actionLabel="개발자 권한 신청"
|
||||||
|
onAction={onAction}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector("h2")?.textContent).toBe("감사 로그");
|
||||||
|
expect(container.textContent).toContain("거부됨");
|
||||||
|
expect(container.textContent).toContain("신청 필요");
|
||||||
|
expect(container.querySelector("button")).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
interface DeveloperAccessRequestCardProps {
|
||||||
|
title: string;
|
||||||
|
isPending: boolean;
|
||||||
|
canRequest: boolean;
|
||||||
|
pendingMessage: string;
|
||||||
|
deniedMessage: string;
|
||||||
|
pendingDetailMessage: string;
|
||||||
|
deniedDetailMessage: string;
|
||||||
|
actionLabel: string;
|
||||||
|
onAction: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeveloperAccessRequestCard({
|
||||||
|
title,
|
||||||
|
isPending,
|
||||||
|
canRequest,
|
||||||
|
pendingMessage,
|
||||||
|
deniedMessage,
|
||||||
|
pendingDetailMessage,
|
||||||
|
deniedDetailMessage,
|
||||||
|
actionLabel,
|
||||||
|
onAction,
|
||||||
|
}: DeveloperAccessRequestCardProps) {
|
||||||
|
const showAction = isPending || canRequest;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-xl border border-border/60 bg-card p-8 text-center">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h2 className="text-2xl font-semibold tracking-tight">{title}</h2>
|
||||||
|
<p className="font-medium text-foreground">
|
||||||
|
{isPending ? pendingMessage : deniedMessage}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{isPending ? pendingDetailMessage : deniedDetailMessage}
|
||||||
|
</p>
|
||||||
|
{showAction && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="font-bold text-primary hover:underline"
|
||||||
|
onClick={onAction}
|
||||||
|
>
|
||||||
|
{actionLabel}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import { AuditLogTable } from "../../../../common/core/components/audit";
|
|||||||
import { PageHeader } from "../../../../common/core/components/page";
|
import { PageHeader } from "../../../../common/core/components/page";
|
||||||
import { SearchFilterBar } from "../../../../common/ui/search-filter-bar";
|
import { SearchFilterBar } from "../../../../common/ui/search-filter-bar";
|
||||||
import { ForbiddenMessage } from "../../components/common/ForbiddenMessage";
|
import { ForbiddenMessage } from "../../components/common/ForbiddenMessage";
|
||||||
|
import { DeveloperAccessRequestCard } from "../../components/common/DeveloperAccessRequestCard";
|
||||||
import { Badge } from "../../components/ui/badge";
|
import { Badge } from "../../components/ui/badge";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -94,7 +95,7 @@ function AuditLogsPage() {
|
|||||||
const { data: requestStatus, isLoading: isLoadingRequestStatus } = useQuery({
|
const { data: requestStatus, isLoading: isLoadingRequestStatus } = useQuery({
|
||||||
queryKey: ["developer-request", tenantId],
|
queryKey: ["developer-request", tenantId],
|
||||||
queryFn: () => fetchDeveloperRequestStatus(tenantId),
|
queryFn: () => fetchDeveloperRequestStatus(tenantId),
|
||||||
enabled: hasAccessToken && (profileRole === "user" || profileRole === "tenant_member"),
|
enabled: hasAccessToken && profileRole === "user",
|
||||||
});
|
});
|
||||||
const hasDeveloperAccess =
|
const hasDeveloperAccess =
|
||||||
profileRole === "super_admin" ||
|
profileRole === "super_admin" ||
|
||||||
@@ -103,7 +104,7 @@ function AuditLogsPage() {
|
|||||||
requestStatus?.status === "approved";
|
requestStatus?.status === "approved";
|
||||||
const isDeveloperRequestPending = requestStatus?.status === "pending";
|
const isDeveloperRequestPending = requestStatus?.status === "pending";
|
||||||
const canRequestDeveloperAccess =
|
const canRequestDeveloperAccess =
|
||||||
(profileRole === "user" || profileRole === "tenant_member") &&
|
profileRole === "user" &&
|
||||||
!isLoadingRequestStatus &&
|
!isLoadingRequestStatus &&
|
||||||
!hasDeveloperAccess &&
|
!hasDeveloperAccess &&
|
||||||
!isDeveloperRequestPending;
|
!isDeveloperRequestPending;
|
||||||
@@ -138,7 +139,7 @@ function AuditLogsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(profileRole === "user" || profileRole === "tenant_member") &&
|
profileRole === "user" &&
|
||||||
(isLoadingMe || isLoadingRequestStatus)
|
(isLoadingMe || isLoadingRequestStatus)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
@@ -150,44 +151,29 @@ function AuditLogsPage() {
|
|||||||
|
|
||||||
if (!hasDeveloperAccess) {
|
if (!hasDeveloperAccess) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-border/60 bg-card p-8 text-center">
|
<DeveloperAccessRequestCard
|
||||||
<div className="space-y-3">
|
title={t("ui.common.audit.title", "Audit Logs")}
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
isPending={isDeveloperRequestPending}
|
||||||
{t("ui.common.audit.title", "Audit Logs")}
|
canRequest={canRequestDeveloperAccess}
|
||||||
</h2>
|
pendingMessage={t(
|
||||||
<p className="font-medium text-foreground">
|
"msg.dev.dashboard.access_pending",
|
||||||
{isDeveloperRequestPending
|
"개발자 권한 신청을 검토 중입니다.",
|
||||||
? t(
|
)}
|
||||||
"msg.dev.dashboard.access_pending",
|
deniedMessage={t(
|
||||||
"개발자 권한 신청을 검토 중입니다.",
|
"msg.dev.audit.access_denied",
|
||||||
)
|
"감사 로그는 개발자 권한이 있어야 볼 수 있습니다.",
|
||||||
: t(
|
)}
|
||||||
"msg.dev.dashboard.access_denied",
|
pendingDetailMessage={t(
|
||||||
"대시보드는 개발자 권한이 있어야 볼 수 있습니다.",
|
"msg.dev.dashboard.access_pending_detail",
|
||||||
)}
|
"super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다.",
|
||||||
</p>
|
)}
|
||||||
<p className="text-sm text-muted-foreground">
|
deniedDetailMessage={t(
|
||||||
{isDeveloperRequestPending
|
"msg.dev.audit.access_denied_detail",
|
||||||
? t(
|
"개발자 권한 신청 페이지에서 신청을 등록한 뒤 승인을 받아주세요.",
|
||||||
"msg.dev.dashboard.access_pending_detail",
|
)}
|
||||||
"super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다.",
|
actionLabel={t("ui.dev.nav.developer_request", "개발자 권한 신청")}
|
||||||
)
|
onAction={() => navigate("/developer-requests")}
|
||||||
: t(
|
/>
|
||||||
"msg.dev.dashboard.access_denied_detail",
|
|
||||||
"개발자 권한 신청 페이지에서 신청을 등록한 뒤 승인을 받아주세요.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{(isDeveloperRequestPending || canRequestDeveloperAccess) && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="font-bold text-primary hover:underline"
|
|
||||||
onClick={() => navigate("/developer-requests")}
|
|
||||||
>
|
|
||||||
{t("ui.dev.nav.developer_request", "개발자 권한 신청")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
OverviewMetric,
|
OverviewMetric,
|
||||||
OverviewSelectionChips,
|
OverviewSelectionChips,
|
||||||
} from "../../../../common/core/components/overview";
|
} from "../../../../common/core/components/overview";
|
||||||
|
import { DeveloperAccessRequestCard } from "../../components/common/DeveloperAccessRequestCard";
|
||||||
import {
|
import {
|
||||||
type ClientSummary,
|
type ClientSummary,
|
||||||
fetchClients,
|
fetchClients,
|
||||||
@@ -523,7 +524,7 @@ function GlobalOverviewPage() {
|
|||||||
requestStatus?.status === "approved";
|
requestStatus?.status === "approved";
|
||||||
const isDeveloperRequestPending = requestStatus?.status === "pending";
|
const isDeveloperRequestPending = requestStatus?.status === "pending";
|
||||||
const canRequestDeveloperAccess =
|
const canRequestDeveloperAccess =
|
||||||
(profileRole === "user" || profileRole === "tenant_member") &&
|
profileRole === "user" &&
|
||||||
!isLoadingRequestStatus &&
|
!isLoadingRequestStatus &&
|
||||||
!hasDeveloperAccess &&
|
!hasDeveloperAccess &&
|
||||||
!isDeveloperRequestPending;
|
!isDeveloperRequestPending;
|
||||||
@@ -615,7 +616,7 @@ function GlobalOverviewPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(profileRole === "user" || profileRole === "tenant_member") &&
|
profileRole === "user" &&
|
||||||
(isLoadingMe || isLoadingRequestStatus)
|
(isLoadingMe || isLoadingRequestStatus)
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
@@ -627,44 +628,29 @@ function GlobalOverviewPage() {
|
|||||||
|
|
||||||
if (!hasDeveloperAccess) {
|
if (!hasDeveloperAccess) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-border/60 bg-card p-8 text-center">
|
<DeveloperAccessRequestCard
|
||||||
<div className="space-y-3">
|
title={t("ui.common.overview.title", "운영 현황")}
|
||||||
<h2 className="text-2xl font-semibold tracking-tight">
|
isPending={isDeveloperRequestPending}
|
||||||
{t("ui.common.overview.title", "운영 현황")}
|
canRequest={canRequestDeveloperAccess}
|
||||||
</h2>
|
pendingMessage={t(
|
||||||
<p className="font-medium text-foreground">
|
"msg.dev.dashboard.access_pending",
|
||||||
{isDeveloperRequestPending
|
"개발자 권한 신청을 검토 중입니다.",
|
||||||
? t(
|
)}
|
||||||
"msg.dev.dashboard.access_pending",
|
deniedMessage={t(
|
||||||
"개발자 권한 신청을 검토 중입니다.",
|
"msg.dev.dashboard.access_denied",
|
||||||
)
|
"대시보드는 개발자 권한이 있어야 볼 수 있습니다.",
|
||||||
: t(
|
)}
|
||||||
"msg.dev.dashboard.access_denied",
|
pendingDetailMessage={t(
|
||||||
"대시보드는 개발자 권한이 있어야 볼 수 있습니다.",
|
"msg.dev.dashboard.access_pending_detail",
|
||||||
)}
|
"super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다.",
|
||||||
</p>
|
)}
|
||||||
<p className="text-sm text-muted-foreground">
|
deniedDetailMessage={t(
|
||||||
{isDeveloperRequestPending
|
"msg.dev.dashboard.access_denied_detail",
|
||||||
? t(
|
"개발자 권한 신청 페이지에서 신청을 등록한 뒤 승인을 받아주세요.",
|
||||||
"msg.dev.dashboard.access_pending_detail",
|
)}
|
||||||
"super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다.",
|
actionLabel={t("ui.dev.nav.developer_request", "개발자 권한 신청")}
|
||||||
)
|
onAction={() => navigate("/developer-requests")}
|
||||||
: t(
|
/>
|
||||||
"msg.dev.dashboard.access_denied_detail",
|
|
||||||
"개발자 권한 신청 페이지에서 신청을 등록한 뒤 승인을 받아주세요.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
{(isDeveloperRequestPending || canRequestDeveloperAccess) && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="font-bold text-primary hover:underline"
|
|
||||||
onClick={() => navigate("/developer-requests")}
|
|
||||||
>
|
|
||||||
{t("ui.dev.nav.developer_request", "개발자 권한 신청")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -511,6 +511,10 @@ access_pending = "Your developer access request is under review."
|
|||||||
access_pending_detail = "You can use the overview and developer features after a super admin approves it."
|
access_pending_detail = "You can use the overview and developer features after a super admin approves it."
|
||||||
description = "View connected application composition and authentication operations metrics in one place."
|
description = "View connected application composition and authentication operations metrics in one place."
|
||||||
|
|
||||||
|
[msg.dev.audit]
|
||||||
|
access_denied = "Audit logs are available only to users with developer access."
|
||||||
|
access_denied_detail = "Submit a request on the developer access page and wait for approval."
|
||||||
|
|
||||||
[msg.dev.dashboard.hero]
|
[msg.dev.dashboard.hero]
|
||||||
body = "Body"
|
body = "Body"
|
||||||
title_emphasis = "Title Emphasis"
|
title_emphasis = "Title Emphasis"
|
||||||
|
|||||||
@@ -511,6 +511,10 @@ access_pending = "개발자 권한 신청을 검토 중입니다."
|
|||||||
access_pending_detail = "super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다."
|
access_pending_detail = "super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다."
|
||||||
description = "연동 앱 구성과 인증 운영 지표를 한 곳에서 확인합니다."
|
description = "연동 앱 구성과 인증 운영 지표를 한 곳에서 확인합니다."
|
||||||
|
|
||||||
|
[msg.dev.audit]
|
||||||
|
access_denied = "감사 로그는 개발자 권한이 있어야 볼 수 있습니다."
|
||||||
|
access_denied_detail = "개발자 권한 신청 페이지에서 신청을 등록한 뒤 승인을 받아주세요."
|
||||||
|
|
||||||
[msg.dev.dashboard.hero]
|
[msg.dev.dashboard.hero]
|
||||||
body = "Hydra Admin API와 동기화된 RP 목록, 상태 토글, Consent 회수까지 devfront에서 처리하도록 준비합니다."
|
body = "Hydra Admin API와 동기화된 RP 목록, 상태 토글, Consent 회수까지 devfront에서 처리하도록 준비합니다."
|
||||||
title_emphasis = " 하나의 화면"
|
title_emphasis = " 하나의 화면"
|
||||||
|
|||||||
@@ -549,6 +549,10 @@ access_pending = ""
|
|||||||
access_pending_detail = ""
|
access_pending_detail = ""
|
||||||
description = ""
|
description = ""
|
||||||
|
|
||||||
|
[msg.dev.audit]
|
||||||
|
access_denied = ""
|
||||||
|
access_denied_detail = ""
|
||||||
|
|
||||||
[msg.dev.dashboard.hero]
|
[msg.dev.dashboard.hero]
|
||||||
body = ""
|
body = ""
|
||||||
title_emphasis = ""
|
title_emphasis = ""
|
||||||
|
|||||||
Reference in New Issue
Block a user