Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ui-react/apps/console/src/components/ManageTagsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export default function ManageTagsDrawer({
bodyClassName="flex-1 flex flex-col overflow-hidden"
footer={(
<button
type="button"
onClick={onClose}
className="px-4 py-2.5 text-sm font-medium text-text-secondary hover:text-text-primary rounded-lg hover:bg-hover-subtle transition-colors"
>
Expand Down Expand Up @@ -190,6 +191,7 @@ export default function ManageTagsDrawer({
{error}
</p>
<button
type="button"
onClick={() => setError(null)}
className="p-0.5 rounded text-accent-red/60 hover:text-accent-red transition-colors shrink-0"
>
Expand Down Expand Up @@ -276,6 +278,7 @@ export default function ManageTagsDrawer({
{editingTag !== tag.name && (
<div className="flex items-center gap-0.5 shrink-0">
<button
type="button"
onClick={() => {
setEditingTag(tag.name);
setEditName(tag.name);
Expand All @@ -286,6 +289,7 @@ export default function ManageTagsDrawer({
<PencilSquareIcon className="w-3.5 h-3.5" />
</button>
<button
type="button"
onClick={() => setDeletingTag(tag.name)}
className="p-1.5 rounded-md text-text-muted hover:text-accent-red hover:bg-accent-red/10 transition-all"
title="Delete"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ function CopyBlock({ command }: { command: string }) {
<span className="text-primary/60">$ </span>
{command}
<button
type="button"
onClick={handleCopy}
className="absolute top-2.5 right-2.5 p-1.5 rounded-md text-text-muted hover:text-primary hover:bg-primary/10 transition-all"
title="Copy command"
Expand Down Expand Up @@ -170,6 +171,7 @@ function CommunityInstructions() {
</div>

<button
type="button"
disabled={!ready}
onClick={handleContinue}
className={`w-full flex items-center justify-center gap-2.5 px-5 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export default function CreateNamespaceDialog({
</div>

<button
type="button"
onClick={onClose}
className="p-1.5 rounded-lg text-text-muted hover:text-text-primary hover:bg-hover-medium transition-all"
aria-label="Close dialog"
Expand Down Expand Up @@ -206,6 +207,7 @@ export default function CreateNamespaceDialog({

<div className="flex items-center gap-2">
<button
type="button"
onClick={onClose}
className="px-4 py-2 rounded-lg text-xs font-medium text-text-secondary hover:text-text-primary hover:bg-hover-medium transition-all"
>
Expand Down
18 changes: 9 additions & 9 deletions ui-react/apps/console/src/components/mfa/MfaDisableDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { disableMfa } from "@/client";
import { useOtpInput } from "@/hooks/useOtpInput";
import { useAuthStore } from "@/stores/authStore";
import Spinner from "@/components/common/Spinner";
import BaseDialog from "@/components/common/BaseDialog";

interface MfaDisableDialogProps {
open: boolean;
Expand All @@ -29,8 +30,6 @@ export default function MfaDisableDialog({
const otpRecoveryEmail = useOtpInput(5, true);
const { user, username, requestMfaReset } = useAuthStore();

if (!open) return null;

const handleRequestEmailReset = async (): Promise<void> => {
const identifier = user || username;
if (!identifier) {
Expand Down Expand Up @@ -104,12 +103,13 @@ export default function MfaDisableDialog({
: otpMainEmail.isComplete && otpRecoveryEmail.isComplete;

return (
<div className="fixed inset-0 z-[70] flex items-center justify-center">
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
/>
<div className="relative bg-surface border border-border rounded-2xl w-full max-w-sm mx-4 p-6 shadow-2xl animate-slide-up">
<BaseDialog
open={open}
onClose={onClose}
size="sm"
aria-label="Disable MFA"
>
<div className="p-6">
{/* Header */}
<div className="flex items-start gap-3 mb-4">
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-accent-red/15 border border-accent-red/25 flex items-center justify-center">
Expand Down Expand Up @@ -300,6 +300,6 @@ export default function MfaDisableDialog({
)}
</form>
</div>
</div>
</BaseDialog>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
KeyIcon,
ExclamationTriangleIcon,
} from "@heroicons/react/24/outline";
import BaseDialog from "@/components/common/BaseDialog";

interface MfaRecoveryCodesModalProps {
open: boolean;
Expand All @@ -12,15 +13,14 @@ export default function MfaRecoveryCodesModal({
open,
onClose,
}: MfaRecoveryCodesModalProps) {
if (!open) return null;

return (
<div className="fixed inset-0 z-[70] flex items-center justify-center">
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
/>
<div className="relative bg-surface border border-border rounded-2xl w-full max-w-md mx-4 p-6 shadow-2xl animate-slide-up">
<BaseDialog
open={open}
onClose={onClose}
size="md"
aria-label="Recovery Codes"
>
<div className="p-6">
{/* Header */}
<div className="flex items-start gap-3 mb-4">
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-accent-yellow/15 border border-accent-yellow/25 flex items-center justify-center">
Expand Down Expand Up @@ -56,6 +56,7 @@ export default function MfaRecoveryCodesModal({

<div className="flex justify-end pt-2">
<button
type="button"
onClick={onClose}
className="px-5 py-2 bg-primary hover:bg-primary-600 text-white rounded-lg text-sm font-semibold transition-all"
>
Expand All @@ -64,6 +65,6 @@ export default function MfaRecoveryCodesModal({
</div>
</div>
</div>
</div>
</BaseDialog>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { useCountdown } from "@/hooks/useCountdown";
import CheckboxField from "@/components/common/fields/CheckboxField";
import Spinner from "@/components/common/Spinner";
import BaseDialog from "@/components/common/BaseDialog";

interface MfaRecoveryTimeoutModalProps {
open: boolean;
Expand All @@ -21,8 +22,6 @@ export default function MfaRecoveryTimeoutModal({
const [disabling, setDisabling] = useState(false);
const { timeLeft, isExpired } = useCountdown(expiresAt);

if (!open) return null;

const handleDisable = async () => {
setDisabling(true);
try {
Expand All @@ -33,11 +32,14 @@ export default function MfaRecoveryTimeoutModal({
};

return (
<div className="fixed inset-0 z-[70] flex items-center justify-center">
{/* Non-dismissible backdrop */}
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />

<div className="relative bg-surface border border-border rounded-2xl w-full max-w-md mx-4 p-6 shadow-2xl animate-slide-up">
<BaseDialog
open={open}
onClose={onClose}
canClose={() => false}
size="md"
aria-label="Recovery Window Active"
>
<div className="p-6">
{/* Header */}
<div className="flex items-start gap-3 mb-4">
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-accent-yellow/15 border border-accent-yellow/25 flex items-center justify-center">
Expand Down Expand Up @@ -83,12 +85,14 @@ export default function MfaRecoveryTimeoutModal({
{/* Actions */}
<div className="flex justify-end gap-2">
<button
type="button"
onClick={onClose}
className="px-4 py-2.5 text-sm font-medium text-text-secondary hover:text-text-primary rounded-lg hover:bg-hover-subtle transition-colors"
>
Close
</button>
<button
type="button"
onClick={() => void handleDisable()}
disabled={hasAccess || disabling || isExpired}
className="px-5 py-2.5 bg-accent-red/90 hover:bg-accent-red text-white rounded-lg text-sm font-semibold disabled:opacity-dim disabled:cursor-not-allowed transition-all flex items-center gap-2"
Expand All @@ -110,6 +114,6 @@ export default function MfaRecoveryTimeoutModal({
</p>
</div>
</div>
</div>
</BaseDialog>
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import MfaDisableDialog from "../MfaDisableDialog";

vi.mock("@/client", () => ({
disableMfa: vi.fn(),
}));

vi.mock("@/hooks/useFocusTrap", () => ({ useFocusTrap: vi.fn() }));

import { disableMfa } from "@/client";

const mockedDisableMfa = vi.mocked(disableMfa);
Expand Down Expand Up @@ -249,20 +251,15 @@ describe("MfaDisableDialog", () => {
expect(onSuccess).not.toHaveBeenCalled();
});

it("closes when clicking outside (backdrop)", async () => {
const user = userEvent.setup();
it("closes when clicking outside (backdrop)", () => {
render(
<MfaDisableDialog open={true} onClose={onClose} onSuccess={onSuccess} />,
);

// Get the backdrop (absolute positioned div before the dialog card)
const dialog = screen.getByRole("heading", { name: /Disable MFA/i }).closest(".relative");
const backdrop = dialog?.previousElementSibling;

if (backdrop) {
await user.click(backdrop);
expect(onClose).toHaveBeenCalled();
}
const dialog = document.querySelector("dialog") as HTMLElement;
fireEvent.mouseDown(dialog);
fireEvent.click(dialog);
expect(onClose).toHaveBeenCalled();
});

it("does not render when open is false", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, act, waitFor } from "@testing-library/react";
import { render, screen, act, waitFor, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import MfaRecoveryTimeoutModal from "../MfaRecoveryTimeoutModal";

vi.mock("@/hooks/useFocusTrap", () => ({ useFocusTrap: vi.fn() }));

describe("MfaRecoveryTimeoutModal", () => {
const onClose = vi.fn();
const onDisable = vi.fn();
Expand Down Expand Up @@ -244,8 +246,7 @@ describe("MfaRecoveryTimeoutModal", () => {
expect(onClose).toHaveBeenCalled();
});

it("does not dismiss when clicking the backdrop (non-dismissible)", async () => {
const user = userEvent.setup();
it("does not dismiss when clicking the backdrop (non-dismissible)", () => {
const expiresAt = Math.floor(Date.now() / 1000) + 10 * 60;

render(
Expand All @@ -257,17 +258,11 @@ describe("MfaRecoveryTimeoutModal", () => {
/>,
);

// The backdrop is intentionally non-dismissible (no onClick handler)
// Modal has no role="dialog"; find inner container via heading
const heading = screen.getByText(/recovery window active/i);
const dialog = heading.closest(".relative");
const backdrop = dialog?.previousElementSibling as HTMLElement | null;

if (backdrop) {
await user.click(backdrop);
// onClose should NOT be called — user must explicitly use the Close button
expect(onClose).not.toHaveBeenCalled();
}
// BaseDialog with canClose={() => false} intercepts the cancel event and blocks it
const dialog = document.querySelector("dialog") as HTMLElement;
fireEvent(dialog, new Event("cancel"));
// onClose should NOT be called — user must explicitly use the Close button
expect(onClose).not.toHaveBeenCalled();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export default function VaultSettingsSection() {
</h3>
<div className="bg-card border border-border rounded-lg divide-y divide-border">
<button
type="button"
onClick={() => setChangeOpen(true)}
className="flex items-center gap-3 w-full px-4 py-3.5 text-left hover:bg-hover-subtle transition-colors rounded-t-lg"
>
Expand All @@ -165,6 +166,7 @@ export default function VaultSettingsSection() {
</button>

<button
type="button"
onClick={lock}
className="flex items-center gap-3 w-full px-4 py-3.5 text-left hover:bg-hover-subtle transition-colors"
>
Expand All @@ -180,6 +182,7 @@ export default function VaultSettingsSection() {
</button>

<button
type="button"
onClick={() => {
setResetConfirmText("");
setResetOpen(true);
Expand Down
1 change: 0 additions & 1 deletion ui-react/apps/console/src/pages/ContainerDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ function TagsSection({ uid, tags }: { uid: string; tags: string[] }) {
}}
placeholder="Add tag..."
aria-label="Add tag"
pattern="^[a-zA-Z0-9]+$"
className="w-28 px-2.5 py-1 bg-card border border-border rounded-md text-xs text-text-primary placeholder:text-text-secondary focus:outline-none focus:border-primary/40 transition-all"
/>
<button
Expand Down
Loading
Loading