1
0
forked from baron/baron-sso

개요/감사로그 CTA 공통화

This commit is contained in:
2026-05-29 10:08:13 +09:00
parent 23e3738b80
commit b4dfbe0480
7 changed files with 179 additions and 81 deletions

View File

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

View File

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