[CHAIN] refactor(ui): remove dead tokens from tailwind config (#11533)

This commit is contained in:
Alejandro Bailo
2026-06-17 17:51:51 +02:00
committed by GitHub
parent 600abbae93
commit b17ce4967d
467 changed files with 3258 additions and 6118 deletions
+2 -2
View File
@@ -187,7 +187,7 @@ test("action works", { tag: ["@critical", "@feature"] }, async ({ page }) => {
Next.js 16.2.3 | React 19.2.5 | Tailwind 4.1.18 | shadcn/ui
Zod 4.1.11 | React Hook Form 7.62.0 | Zustand 5.0.8 | NextAuth 5.0.0-beta.30 | Recharts 2.15.4
> **Note**: HeroUI exists in `components/ui/` as legacy code. Do NOT add new components there.
> **Note**: `components/ui/` only holds temporary re-export shims for the prowler-cloud overlay. Do NOT add new components there.
---
@@ -198,7 +198,7 @@ ui/
├── app/(auth)/ # Auth pages
├── app/(prowler)/ # Main app: compliance, findings, providers, scans
├── components/shadcn/ # shadcn/ui components (USE THIS)
├── components/ui/ # HeroUI (LEGACY - do not add here)
├── components/ui/ # Cloud-overlay re-export shims (do not add here)
├── actions/ # Server actions
├── types/ # Shared types
├── hooks/ # Shared hooks
+3 -4
View File
@@ -114,10 +114,9 @@ pnpm run dev
## Technologies Used
- [Next.js 14](https://nextjs.org/docs/getting-started)
- [NextUI v2](https://nextui.org/)
- [Tailwind CSS](https://tailwindcss.com/)
- [Tailwind Variants](https://tailwind-variants.org)
- [Next.js 16](https://nextjs.org/docs/getting-started)
- [shadcn/ui](https://ui.shadcn.com/) (built on [Radix UI](https://www.radix-ui.com/))
- [Tailwind CSS 4](https://tailwindcss.com/)
- [TypeScript](https://www.typescriptlang.org/)
- [Framer Motion](https://www.framer.com/motion/)
- [next-themes](https://github.com/pacocoursey/next-themes)
+2 -1
View File
@@ -16,7 +16,8 @@ vi.mock("@/components/auth/oss/auth-layout", () => ({
),
}));
vi.mock("@/components/shadcn", () => ({
vi.mock("@/components/shadcn", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
Button: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}));
@@ -16,7 +16,8 @@ vi.mock("@/components/auth/oss/auth-layout", () => ({
),
}));
vi.mock("@/components/shadcn", () => ({
vi.mock("@/components/shadcn", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
Button: ({ children }: { children: ReactNode }) => <div>{children}</div>,
}));
@@ -74,11 +74,11 @@ export function AcceptInvitationClient({
<div className="flex flex-col items-center gap-4">
<Icon
icon="solar:danger-triangle-bold"
className="text-warning"
className="text-text-warning-primary"
width={48}
/>
<h1 className="text-xl font-semibold">Invalid Invitation Link</h1>
<p className="text-default-500">
<p className="text-text-neutral-tertiary">
No invitation token was provided. Please check the link you
received.
</p>
@@ -93,11 +93,11 @@ export function AcceptInvitationClient({
<div className="flex flex-col items-center gap-4">
<Icon
icon="eos-icons:loading"
className="text-default-500"
className="text-text-neutral-tertiary"
width={48}
/>
<h1 className="text-xl font-semibold">Accepting Invitation...</h1>
<p className="text-default-500">
<p className="text-text-neutral-tertiary">
Please wait while we process your invitation.
</p>
</div>
@@ -108,13 +108,13 @@ export function AcceptInvitationClient({
<div className="flex flex-col items-center gap-4">
<Icon
icon="solar:danger-triangle-bold"
className="text-danger"
className="text-text-error-primary"
width={48}
/>
<h1 className="text-xl font-semibold">
Could Not Accept Invitation
</h1>
<p className="text-default-500">{state.message}</p>
<p className="text-text-neutral-tertiary">{state.message}</p>
<div className="flex gap-3">
{state.canRetry && <Button onClick={doAccept}>Retry</Button>}
<Button asChild variant="outline">
@@ -129,14 +129,14 @@ export function AcceptInvitationClient({
<div className="flex flex-col items-center gap-6">
<Icon
icon="solar:letter-bold"
className="text-primary"
className="text-button-primary"
width={48}
/>
<div>
<h1 className="text-xl font-semibold">
You&apos;ve Been Invited
</h1>
<p className="text-default-500 mt-2">
<p className="text-text-neutral-tertiary mt-2">
You&apos;ve been invited to join a tenant on Prowler. How would
you like to continue?
</p>
+4 -3
View File
@@ -4,8 +4,8 @@ import { GoogleTagManager } from "@next/third-parties/google";
import { Metadata, Viewport } from "next";
import { ReactNode, Suspense } from "react";
import { NavigationProgress, Toaster } from "@/components/ui";
import { fontSans } from "@/config/fonts";
import { NavigationProgress, Toaster } from "@/components/shadcn";
import { fontMono, fontSans } from "@/config/fonts";
import { siteConfig } from "@/config/site";
import { cn } from "@/lib";
@@ -36,8 +36,9 @@ export default function AuthLayout({ children }: { children: ReactNode }) {
<body
suppressHydrationWarning
className={cn(
"bg-background min-h-screen font-sans antialiased",
"bg-bg-neutral-primary min-h-screen font-sans antialiased",
fontSans.variable,
fontMono.variable,
)}
>
<Providers themeProps={{ attribute: "class", defaultTheme: "dark" }}>
@@ -5,7 +5,7 @@ import { LighthouseBanner } from "@/components/lighthouse/banner";
import { LinkToFindings } from "@/components/overview";
import { ColumnLatestFindings } from "@/components/overview/new-findings-table/table";
import { CardTitle } from "@/components/shadcn";
import { DataTable } from "@/components/ui/table";
import { DataTable } from "@/components/shadcn/table";
import { FINDINGS_FILTERED_SORT } from "@/lib";
import { createDict } from "@/lib/helper";
import { FindingProps, SearchParamsProps } from "@/types";
@@ -1,7 +1,7 @@
import { Skeleton } from "@heroui/skeleton";
import { Suspense } from "react";
import { SkeletonTableNewFindings } from "@/components/overview/new-findings-table/table";
import { Skeleton } from "@/components/shadcn";
import { SearchParamsProps } from "@/types";
import { GraphsTabsClient } from "./_components/graphs-tabs-client";
@@ -39,7 +39,7 @@ vi.mock(
}),
);
vi.mock("@/components/ui/entities/entity-info", () => ({
vi.mock("@/components/shadcn/entities/entity-info", () => ({
EntityInfo: ({
entityAlias,
entityId,
@@ -56,11 +56,9 @@ vi.mock("next/navigation", () => ({
useSearchParams: () => new URLSearchParams(routerMocks.currentSearch),
}));
vi.mock("@/components/ui", () => ({
vi.mock("@/components/shadcn", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
useToast: () => ({ toast: toastMock }),
}));
vi.mock("@/components/shadcn", () => ({
Button: ({
asChild,
children,
@@ -22,7 +22,7 @@ vi.mock("next/navigation", () => ({
useSearchParams: () => new URLSearchParams(navigationMocks.currentSearch),
}));
vi.mock("@/components/ui/table/data-table", () => ({
vi.mock("@/components/shadcn/table/data-table", () => ({
DataTable: ({
columns,
data,
@@ -81,7 +81,7 @@ vi.mock("@/components/ui/table/data-table", () => ({
),
}));
vi.mock("@/components/ui/table/data-table-column-header", () => ({
vi.mock("@/components/shadcn/table/data-table-column-header", () => ({
DataTableColumnHeader: ({ title }: { title: string }) => <span>{title}</span>,
}));
@@ -25,7 +25,8 @@ vi.mock("next/navigation", () => ({
useRouter: () => routerMocks,
}));
vi.mock("@/components/ui", () => ({
vi.mock("@/components/shadcn", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
ToastAction: ({
asChild,
children,
@@ -16,8 +16,8 @@ import {
type AlertRule,
} from "@/app/(prowler)/alerts/_types";
import { Button } from "@/components/shadcn";
import { useToast } from "@/components/shadcn";
import { Modal } from "@/components/shadcn/modal";
import { useToast } from "@/components/ui";
import { DOCS_URLS } from "@/lib/external-urls";
import type { MetaDataProps } from "@/types";
import type { ScanEntity } from "@/types";
@@ -9,9 +9,9 @@ import {
ActionDropdownDangerZone,
ActionDropdownItem,
} from "@/components/shadcn/dropdown";
import { DateWithTime } from "@/components/ui/entities";
import { DataTable } from "@/components/ui/table/data-table";
import { DataTableColumnHeader } from "@/components/ui/table/data-table-column-header";
import { DateWithTime } from "@/components/shadcn/entities";
import { DataTable } from "@/components/shadcn/table/data-table";
import { DataTableColumnHeader } from "@/components/shadcn/table/data-table-column-header";
import type { MetaDataProps } from "@/types";
interface AlertsTableProps {
@@ -28,8 +28,8 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/shadcn";
import { ToastAction, useToast } from "@/components/shadcn";
import { CloudFeatureBadgeLink } from "@/components/shared/cloud-feature-badge";
import { ToastAction, useToast } from "@/components/ui";
import type { ScanEntity } from "@/types";
import type { ProviderProps } from "@/types/providers";
+1 -1
View File
@@ -5,7 +5,7 @@ import { getAllProviders } from "@/actions/providers";
import { getScans } from "@/actions/scans";
import { getAlert, listAlerts } from "@/app/(prowler)/alerts/_actions";
import { AlertsManager } from "@/app/(prowler)/alerts/_components/alerts-manager";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { createScanDetailsMapping } from "@/lib";
import type { MetaDataProps, ScanEntity, ScanProps } from "@/types";
@@ -128,45 +128,44 @@ export const VerticalSteps = forwardRef<HTMLButtonElement, VerticalStepsProps>(
"[--active-color:var(--step-color)]",
"[--complete-background-color:var(--step-color)]",
"[--complete-border-color:var(--step-color)]",
"[--inactive-border-color:hsl(var(--heroui-default-300))]",
"[--inactive-color:hsl(var(--heroui-default-300))]",
"[--inactive-border-color:var(--border-neutral-tertiary)]",
"[--inactive-color:var(--border-neutral-tertiary)]",
];
switch (color) {
case "primary":
userColor = "[--step-color:hsl(var(--heroui-primary))]";
fgColor = "[--step-fg-color:hsl(var(--heroui-primary-foreground))]";
break;
case "secondary":
userColor = "[--step-color:hsl(var(--heroui-secondary))]";
fgColor = "[--step-fg-color:hsl(var(--heroui-secondary-foreground))]";
userColor =
"[--step-color:var(--color-violet-600)] dark:[--step-color:var(--color-violet-400)]";
fgColor = "[--step-fg-color:var(--color-white)]";
break;
case "success":
userColor = "[--step-color:hsl(var(--heroui-success))]";
fgColor = "[--step-fg-color:hsl(var(--heroui-success-foreground))]";
userColor = "[--step-color:var(--bg-pass-primary)]";
fgColor = "[--step-fg-color:var(--color-black)]";
break;
case "warning":
userColor = "[--step-color:hsl(var(--heroui-warning))]";
fgColor = "[--step-fg-color:hsl(var(--heroui-warning-foreground))]";
userColor = "[--step-color:var(--bg-warning-primary)]";
fgColor = "[--step-fg-color:var(--color-black)]";
break;
case "danger":
userColor = "[--step-color:hsl(var(--heroui-error))]";
fgColor = "[--step-fg-color:hsl(var(--heroui-error-foreground))]";
userColor = "[--step-color:var(--bg-fail-primary)]";
fgColor = "[--step-fg-color:var(--color-white)]";
break;
case "default":
userColor = "[--step-color:hsl(var(--heroui-default))]";
fgColor = "[--step-fg-color:hsl(var(--heroui-default-foreground))]";
userColor =
"[--step-color:var(--color-zinc-300)] dark:[--step-color:var(--color-zinc-600)]";
fgColor = "[--step-fg-color:var(--text-neutral-primary)]";
break;
case "primary":
default:
userColor = "[--step-color:hsl(var(--heroui-primary))]";
fgColor = "[--step-fg-color:hsl(var(--heroui-primary-foreground))]";
userColor = "[--step-color:var(--bg-button-primary)]";
fgColor = "[--step-fg-color:var(--color-black)]";
break;
}
if (!className?.includes("--step-fg-color")) colorsVars.unshift(fgColor);
if (!className?.includes("--step-color")) colorsVars.unshift(userColor);
if (!className?.includes("--inactive-bar-color"))
colorsVars.push("[--inactive-bar-color:hsl(var(--heroui-default-300))]");
colorsVars.push("[--inactive-bar-color:var(--border-neutral-tertiary)]");
const colors = colorsVars;
@@ -189,7 +188,7 @@ export const VerticalSteps = forwardRef<HTMLButtonElement, VerticalStepsProps>(
ref={ref}
aria-current={status === "active" ? "step" : undefined}
className={cn(
"group rounded-large flex w-full cursor-pointer items-center justify-center gap-4 px-3 py-2.5",
"group flex w-full cursor-pointer items-center justify-center gap-4 rounded-[14px] px-3 py-2.5",
stepClassName,
)}
onClick={() => setCurrentStep(stepIdx)}
@@ -201,7 +200,7 @@ export const VerticalSteps = forwardRef<HTMLButtonElement, VerticalStepsProps>(
<m.div
animate={status}
className={cn(
"border-medium text-large text-default-foreground relative flex h-[34px] w-[34px] items-center justify-center rounded-full font-semibold",
"text-text-neutral-primary relative flex h-[34px] w-[34px] items-center justify-center rounded-full border-2 text-lg font-semibold",
{
"shadow-lg": status === "complete",
},
@@ -242,9 +241,10 @@ export const VerticalSteps = forwardRef<HTMLButtonElement, VerticalStepsProps>(
<div>
<div
className={cn(
"text-medium text-default-foreground font-medium transition-[color,opacity] duration-300 group-active:opacity-70",
"text-text-neutral-primary text-base font-medium transition-[color,opacity] duration-300 group-active:opacity-70",
{
"text-default-500": status === "inactive",
"text-text-neutral-tertiary":
status === "inactive",
},
)}
>
@@ -252,9 +252,10 @@ export const VerticalSteps = forwardRef<HTMLButtonElement, VerticalStepsProps>(
</div>
<div
className={cn(
"text-tiny text-default-600 lg:text-small transition-[color,opacity] duration-300 group-active:opacity-70",
"text-text-neutral-secondary text-xs transition-[color,opacity] duration-300 group-active:opacity-70 lg:text-sm",
{
"text-default-500": status === "inactive",
"text-text-neutral-tertiary":
status === "inactive",
},
)}
>
@@ -38,7 +38,7 @@ export const WorkflowAttackPaths = () => {
style={{ width: `${progressPercentage}%` }}
/>
</div>
<h3 className="dark:text-prowler-theme-pale/90 text-sm font-semibold">
<h3 className="dark:text-text-neutral-primary/90 text-sm font-semibold">
Step {currentStep + 1} of {steps.length}
</h3>
</div>
@@ -7,7 +7,7 @@ import type { GraphNode } from "@/types/attack-paths";
import { NodeDetailPanel } from "./node-detail-panel";
vi.mock("@/components/ui/sheet/sheet", () => ({
vi.mock("@/components/shadcn/sheet/sheet", () => ({
Sheet: ({ children }: { children: ReactNode }) => <div>{children}</div>,
SheetContent: ({ children }: { children: ReactNode }) => (
<div>{children}</div>
@@ -1,14 +1,14 @@
"use client";
import { Button, Card, CardContent } from "@/components/shadcn";
import { Spinner } from "@/components/shadcn/spinner/spinner";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet/sheet";
} from "@/components/shadcn/sheet/sheet";
import { Spinner } from "@/components/shadcn/spinner/spinner";
import type { GraphNode } from "@/types/attack-paths";
import { NodeFindings } from "./node-findings";
@@ -46,7 +46,7 @@ export const NodeDetailContent = ({
{/* Node Overview Section */}
<Card className="border-border-neutral-secondary">
<CardContent className="flex flex-col gap-3 p-4">
<h3 className="dark:text-prowler-theme-pale/90 text-sm font-semibold">
<h3 className="dark:text-text-neutral-primary/90 text-sm font-semibold">
Node Overview
</h3>
<NodeOverview node={node} />
@@ -57,7 +57,7 @@ export const NodeDetailContent = ({
{!isProwlerFinding && (
<Card className="border-border-neutral-secondary">
<CardContent className="flex flex-col gap-3 p-4">
<h3 className="dark:text-prowler-theme-pale/90 text-sm font-semibold">
<h3 className="dark:text-text-neutral-primary/90 text-sm font-semibold">
Related Findings
</h3>
<div className="text-text-neutral-secondary dark:text-text-neutral-secondary text-xs">
@@ -77,7 +77,7 @@ export const NodeDetailContent = ({
{isProwlerFinding && (
<Card className="border-border-neutral-secondary">
<CardContent className="flex flex-col gap-3 p-4">
<h3 className="dark:text-prowler-theme-pale/90 text-sm font-semibold">
<h3 className="dark:text-text-neutral-primary/90 text-sm font-semibold">
Affected Resources
</h3>
<div className="text-text-neutral-secondary dark:text-text-neutral-secondary text-xs">
@@ -112,7 +112,7 @@ export const NodeDetailPanel = ({
return (
<Sheet open={isOpen} onOpenChange={(open) => !open && onClose?.()}>
<SheetContent className="dark:bg-prowler-theme-midnight my-4 max-h-[calc(100vh-2rem)] max-w-[95vw] overflow-y-auto rounded-l-xl pt-10 md:my-8 md:max-h-[calc(100vh-4rem)] md:max-w-[55vw]">
<SheetContent className="my-4 max-h-[calc(100vh-2rem)] max-w-[95vw] overflow-y-auto rounded-l-xl pt-10 md:my-8 md:max-h-[calc(100vh-4rem)] md:max-w-[55vw]">
<SheetHeader>
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
@@ -2,7 +2,7 @@
import { Button } from "@/components/shadcn";
import { Spinner } from "@/components/shadcn/spinner/spinner";
import { SeverityBadge } from "@/components/ui/table/severity-badge";
import { SeverityBadge } from "@/components/shadcn/table/severity-badge";
import type { GraphNode } from "@/types/attack-paths";
const SEVERITY_LEVELS = {
@@ -80,7 +80,7 @@ export const NodeFindings = ({
severity={normalizeSeverity(finding.properties.severity)}
/>
)}
<h5 className="dark:text-prowler-theme-pale/90 text-sm font-medium">
<h5 className="dark:text-text-neutral-primary/90 text-sm font-medium">
{findingName}
</h5>
</div>
@@ -1,8 +1,8 @@
"use client";
import { CodeSnippet } from "@/components/shadcn/code-snippet/code-snippet";
import { DateWithTime } from "@/components/shadcn/entities/date-with-time";
import { InfoField } from "@/components/shadcn/info-field/info-field";
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
import { DateWithTime } from "@/components/ui/entities/date-with-time";
import type { GraphNode, GraphNodePropertyValue } from "@/types/attack-paths";
import { formatNodeLabels } from "../../_lib";
@@ -47,7 +47,7 @@ export const NodeOverview = ({ node }: NodeOverviewProps) => {
{/* Display all properties */}
<div className="mt-4 border-t border-gray-200 pt-4 dark:border-gray-700">
<h4 className="dark:text-prowler-theme-pale/90 mb-3 text-sm font-semibold">
<h4 className="dark:text-text-neutral-primary/90 mb-3 text-sm font-semibold">
Properties
</h4>
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
@@ -55,7 +55,7 @@ export const NodeRemediation = ({
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
<h5 className="dark:text-prowler-theme-pale/90 text-sm font-medium">
<h5 className="dark:text-text-neutral-primary/90 text-sm font-medium">
{finding.title}
</h5>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
@@ -63,7 +63,7 @@ export const NodeResources = ({ node, allNodes = [] }: NodeResourcesProps) => {
{resource.labels[0]}
</Badge>
)}
<h5 className="dark:text-prowler-theme-pale/90 text-sm font-medium">
<h5 className="dark:text-text-neutral-primary/90 text-sm font-medium">
{String(resource.properties?.name || resourceId)}
</h5>
</div>
@@ -9,7 +9,7 @@ import {
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
} from "@/components/shadcn/form";
import { cn } from "@/lib/utils";
import {
type AttackPathQuery,
@@ -37,7 +37,7 @@ export const QueryParametersForm = ({
return (
<div className="flex flex-col gap-4">
<h3 className="dark:text-prowler-theme-pale/90 text-sm font-semibold">
<h3 className="dark:text-text-neutral-primary/90 text-sm font-semibold">
Query Parameters
</h3>
@@ -63,7 +63,7 @@ export const QueryParametersForm = ({
}
onChange={(e) => field.onChange(e.target.checked)}
aria-label={param.label}
className="border-border-neutral-secondary bg-bg-neutral-primary text-text-primary focus:ring-primary dark:border-border-neutral-secondary dark:bg-bg-neutral-primary dark:text-text-primary h-4 w-4 rounded border focus:ring-2"
className="border-border-neutral-secondary bg-bg-neutral-primary text-text-primary focus:ring-button-primary dark:border-border-neutral-secondary dark:bg-bg-neutral-primary dark:text-text-primary h-4 w-4 rounded border focus:ring-2"
/>
</FormControl>
<div className="flex flex-col gap-1">
@@ -37,7 +37,7 @@ vi.mock("@/components/shadcn/tooltip", () => ({
),
}));
vi.mock("@/components/ui/entities/entity-info", () => ({
vi.mock("@/components/shadcn/entities/entity-info", () => ({
EntityInfo: ({
entityAlias,
entityId,
@@ -47,11 +47,11 @@ vi.mock("@/components/ui/entities/entity-info", () => ({
}) => <div>{entityAlias ?? entityId}</div>,
}));
vi.mock("@/components/ui/entities/date-with-time", () => ({
vi.mock("@/components/shadcn/entities/date-with-time", () => ({
DateWithTime: ({ dateTime }: { dateTime: string }) => <span>{dateTime}</span>,
}));
vi.mock("@/components/ui/table", () => ({
vi.mock("@/components/shadcn/table", () => ({
DataTableColumnHeader: ({ title }: { title: string }) => <span>{title}</span>,
DataTable: ({
columns,
@@ -5,18 +5,18 @@ import { Check, Minus } from "lucide-react";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useRef } from "react";
import { DateWithTime } from "@/components/shadcn/entities/date-with-time";
import { EntityInfo } from "@/components/shadcn/entities/entity-info";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/shadcn/radio-group/radio-group";
import { DataTable, DataTableColumnHeader } from "@/components/shadcn/table";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/shadcn/tooltip";
import { DateWithTime } from "@/components/ui/entities/date-with-time";
import { EntityInfo } from "@/components/ui/entities/entity-info";
import { DataTable, DataTableColumnHeader } from "@/components/ui/table";
import { formatDuration } from "@/lib/date-utils";
import { cn } from "@/lib/utils";
import type { MetaDataProps, ProviderType } from "@/types";
@@ -23,6 +23,7 @@ import {
AlertTitle,
Button,
} from "@/components/shadcn";
import { useToast } from "@/components/shadcn";
import {
Dialog,
DialogContent,
@@ -31,7 +32,6 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/shadcn/dialog";
import { useToast } from "@/components/ui";
import type {
AttackPathQuery,
AttackPathQueryError,
@@ -387,7 +387,7 @@ export default function AttackPathsPage() {
</div>
{scansLoading ? (
<div className="minimal-scrollbar rounded-large shadow-small border-border-neutral-secondary bg-bg-neutral-secondary relative z-0 flex w-full flex-col gap-4 overflow-auto border p-4">
<div className="minimal-scrollbar border-border-neutral-secondary bg-bg-neutral-secondary relative z-0 flex w-full flex-col gap-4 overflow-auto rounded-[14px] border p-4 shadow-sm">
<p className="text-sm">Loading scans...</p>
</div>
) : scans.length === 0 ? (
@@ -427,7 +427,7 @@ export default function AttackPathsPage() {
{/* Query Builder Section - shown only after selecting a scan */}
{scanId && (
<div className="minimal-scrollbar rounded-large shadow-small border-border-neutral-secondary bg-bg-neutral-secondary relative z-0 flex w-full flex-col gap-4 overflow-auto border p-4">
<div className="minimal-scrollbar border-border-neutral-secondary bg-bg-neutral-secondary relative z-0 flex w-full flex-col gap-4 overflow-auto rounded-[14px] border p-4 shadow-sm">
{queriesLoading ? (
<p className="text-sm">Loading queries...</p>
) : queriesError ? (
@@ -481,7 +481,7 @@ export default function AttackPathsPage() {
(graphState.data &&
graphState.data.nodes &&
graphState.data.nodes.length > 0)) && (
<div className="minimal-scrollbar rounded-large shadow-small border-border-neutral-secondary bg-bg-neutral-secondary relative z-0 flex w-full flex-col gap-4 overflow-auto border p-4">
<div className="minimal-scrollbar border-border-neutral-secondary bg-bg-neutral-secondary relative z-0 flex w-full flex-col gap-4 overflow-auto rounded-[14px] border p-4 shadow-sm">
{graphState.loading ? (
<GraphLoading />
) : graphState.data &&
+1 -1
View File
@@ -1,4 +1,4 @@
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
export default function AttackPathsLayout({
children,
@@ -1,4 +1,3 @@
import { Spacer } from "@heroui/spacer";
import { Suspense } from "react";
import {
@@ -25,7 +24,7 @@ import {
TopFailedSectionsCardSkeleton,
} from "@/components/compliance";
import { getComplianceIcon } from "@/components/icons/compliance/IconCompliance";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { getComplianceMapper } from "@/lib/compliance/compliance-mapper";
import {
getReportTypeForCompliance,
@@ -350,7 +349,7 @@ const SSRComplianceContent = async ({
{/* <SectionsFailureRateCard categories={categoryHeatmapData} /> */}
</div>
<Spacer className="bg-border-neutral-primary h-1 w-full rounded-full" />
<div className="bg-border-neutral-primary h-1 w-full rounded-full" />
<ClientAccordionWrapper
hideExpandButton={complianceId.includes("mitre_attack")}
items={accordionItems}
+2 -2
View File
@@ -16,7 +16,7 @@ import { ComplianceFilters } from "@/components/compliance/compliance-header/com
import { ComplianceOverviewGrid } from "@/components/compliance/compliance-overview-grid";
import { Alert, AlertDescription } from "@/components/shadcn/alert";
import { Card, CardContent } from "@/components/shadcn/card/card";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { pickLatestCisPerProvider } from "@/lib/compliance/compliance-report-types";
import {
ExpandedScanData,
@@ -266,7 +266,7 @@ const ComplianceOverviewPanel = ({
<Card
variant="base"
padding="none"
className="minimal-scrollbar shadow-small relative z-0 w-full gap-4 overflow-auto"
className="minimal-scrollbar relative z-0 w-full gap-4 overflow-auto shadow-sm"
>
<CardContent className="flex flex-col gap-4 p-4">{children}</CardContent>
</Card>
@@ -4,9 +4,9 @@ import { ColumnDef } from "@tanstack/react-table";
import { CloudIcon, FolderIcon, ServerIcon } from "lucide-react";
import { notFound } from "next/navigation";
import { DataTable } from "@/components/ui/table/data-table";
import { DataTableExpandAllToggle } from "@/components/ui/table/data-table-expand-all-toggle";
import { DataTableExpandableCell } from "@/components/ui/table/data-table-expandable-cell";
import { DataTable } from "@/components/shadcn/table/data-table";
import { DataTableExpandAllToggle } from "@/components/shadcn/table/data-table-expand-all-toggle";
import { DataTableExpandableCell } from "@/components/shadcn/table/data-table-expandable-cell";
/**
* Demo page for the Expandable DataTable component.
+1 -1
View File
@@ -135,7 +135,7 @@ export default function DemoTreeViewPage() {
<TooltipContent side="top">{item.name}</TooltipContent>
</Tooltip>
{hasChildren && !isLeaf && (
<span className="bg-prowler-white/10 inline-flex min-w-5 shrink-0 items-center justify-center rounded px-1 py-0.5 text-xs tabular-nums">
<span className="inline-flex min-w-5 shrink-0 items-center justify-center rounded bg-white/10 px-1 py-0.5 text-xs tabular-nums">
{item.children?.length}
</span>
)}
+1 -1
View File
@@ -12,7 +12,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/shadcn/card/card";
import { CustomLink } from "@/components/ui/custom/custom-link";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
import { SentryErrorSource, SentryErrorType } from "@/sentry";
export default function Error({
+2 -2
View File
@@ -14,7 +14,7 @@ import {
FindingsGroupTable,
SkeletonTableFindings,
} from "@/components/findings/table";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { FilterTransitionWrapper } from "@/contexts";
import {
applyDefaultMutedFilter,
@@ -154,7 +154,7 @@ const SSRDataTable = async ({
return (
<>
{findingGroupsData?.errors?.length > 0 && (
<div className="text-small mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-red-700">
<div className="mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-sm text-red-700">
<p className="mr-2 font-semibold">Error:</p>
<p>{findingGroupsData.errors[0].detail}</p>
</div>
@@ -4,7 +4,7 @@ import { getIntegrations } from "@/actions/integrations";
import { getAllProviders } from "@/actions/providers";
import { S3IntegrationsManager } from "@/components/integrations/s3/s3-integrations-manager";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/shadcn";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
interface S3IntegrationsProps {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
@@ -4,7 +4,7 @@ import { getIntegrations } from "@/actions/integrations";
import { getAllProviders } from "@/actions/providers";
import { SecurityHubIntegrationsManager } from "@/components/integrations/security-hub/security-hub-integrations-manager";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/shadcn";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
interface SecurityHubIntegrationsProps {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
+1 -1
View File
@@ -1,7 +1,7 @@
import { getIntegrations } from "@/actions/integrations";
import { JiraIntegrationsManager } from "@/components/integrations/jira/jira-integrations-manager";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/shadcn";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
interface JiraIntegrationsProps {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
+1 -1
View File
@@ -5,7 +5,7 @@ import {
SecurityHubIntegrationCard,
SsoLinkCard,
} from "@/components/integrations";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
export default async function Integrations() {
return (
@@ -1,10 +1,9 @@
import "@/styles/globals.css";
import { Spacer } from "@heroui/spacer";
import React from "react";
import { WorkflowSendInvite } from "@/components/invitations/workflow";
import { NavigationHeader } from "@/components/ui";
import { NavigationHeader } from "@/components/shadcn";
interface InvitationLayoutProps {
children: React.ReactNode;
@@ -18,7 +17,7 @@ export default function InvitationLayout({ children }: InvitationLayoutProps) {
icon="icon-park-outline:close-small"
href="/invitations"
/>
<Spacer y={16} />
<div className="h-16" />
<div className="grid grid-cols-1 gap-8 px-4 lg:grid-cols-12 lg:px-0">
<div className="order-1 my-auto h-full lg:col-span-4 lg:col-start-2">
<WorkflowSendInvite />
+2 -2
View File
@@ -11,8 +11,8 @@ import {
SkeletonTableInvitation,
} from "@/components/invitations/table";
import { Button } from "@/components/shadcn";
import { ContentLayout } from "@/components/ui";
import { DataTable, DataTableFilterCustom } from "@/components/ui/table";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { DataTable, DataTableFilterCustom } from "@/components/shadcn/table";
import { InvitationProps, Role, SearchParamsProps } from "@/types";
export default async function Invitations({
+6 -5
View File
@@ -5,10 +5,10 @@ import { Metadata, Viewport } from "next";
import { ReactNode } from "react";
import { getProviders } from "@/actions/providers";
import MainLayout from "@/components/ui/main-layout/main-layout";
import { NavigationProgress } from "@/components/ui/navigation-progress";
import { Toaster } from "@/components/ui/toast";
import { fontSans } from "@/config/fonts";
import MainLayout from "@/components/layout/main-layout/main-layout";
import { NavigationProgress } from "@/components/shadcn/navigation-progress";
import { Toaster } from "@/components/shadcn/toast";
import { fontMono, fontSans } from "@/config/fonts";
import { siteConfig } from "@/config/site";
import { cn } from "@/lib/utils";
import { StoreInitializer } from "@/store/ui/store-initializer";
@@ -50,8 +50,9 @@ export default async function RootLayout({
<body
suppressHydrationWarning
className={cn(
"bg-background min-h-screen font-sans antialiased",
"bg-bg-neutral-primary min-h-screen font-sans antialiased",
fontSans.variable,
fontMono.variable,
)}
>
<Providers themeProps={{ attribute: "class", defaultTheme: "dark" }}>
@@ -2,7 +2,6 @@
import "@/styles/globals.css";
import { Spacer } from "@heroui/spacer";
import { Icon } from "@iconify/react";
import { useRouter, useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react";
@@ -14,8 +13,8 @@ import {
import { DeleteLLMProviderForm } from "@/components/lighthouse/forms/delete-llm-provider-form";
import { WorkflowConnectLLM } from "@/components/lighthouse/workflow";
import { Button } from "@/components/shadcn";
import { NavigationHeader } from "@/components/shadcn";
import { Modal } from "@/components/shadcn/modal";
import { NavigationHeader } from "@/components/ui";
import type { LighthouseProvider } from "@/types/lighthouse";
interface ConnectLLMLayoutProps {
@@ -80,7 +79,7 @@ export default function ConnectLLMLayout({ children }: ConnectLLMLayoutProps) {
icon="icon-park-outline:close-small"
href="/lighthouse/config"
/>
<Spacer y={8} />
<div className="h-8" />
<div className="grid grid-cols-1 gap-8 lg:grid-cols-12">
<div className="order-1 my-auto hidden h-full lg:col-span-4 lg:col-start-2 lg:block">
<WorkflowConnectLLM />
@@ -113,7 +112,7 @@ export default function ConnectLLMLayout({ children }: ConnectLLMLayoutProps) {
Delete Provider
</Button>
</div>
<Spacer y={4} />
<div className="h-4" />
</>
)}
{children}
+2 -4
View File
@@ -1,7 +1,5 @@
import { Spacer } from "@heroui/spacer";
import { LighthouseSettings, LLMProvidersTable } from "@/components/lighthouse";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
export const dynamic = "force-dynamic";
@@ -9,7 +7,7 @@ export default async function ChatbotConfigPage() {
return (
<ContentLayout title="LLM Configuration">
<LLMProvidersTable />
<Spacer y={8} />
<div className="h-8" />
<LighthouseSettings />
</ContentLayout>
);
+1 -1
View File
@@ -6,7 +6,7 @@ import {
} from "@/actions/lighthouse/lighthouse";
import { LighthouseIcon } from "@/components/icons/Icons";
import { Chat } from "@/components/lighthouse";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
export const dynamic = "force-dynamic";
+1 -1
View File
@@ -2,7 +2,7 @@ import "@/styles/globals.css";
import React from "react";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
interface ProviderLayoutProps {
children: React.ReactNode;
@@ -1,6 +1,5 @@
"use client";
import { Textarea } from "@heroui/input";
import { Trash2 } from "lucide-react";
import { useActionState, useEffect, useState } from "react";
@@ -10,11 +9,18 @@ import {
getMutedFindingsConfig,
updateMutedFindingsConfig,
} from "@/actions/processors";
import { Button, Card, Skeleton } from "@/components/shadcn";
import {
Button,
Card,
FieldError,
Skeleton,
Textarea,
} from "@/components/shadcn";
import { useToast } from "@/components/shadcn";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
import { Modal } from "@/components/shadcn/modal";
import { useToast } from "@/components/ui";
import { CustomLink } from "@/components/ui/custom/custom-link";
import { fontMono } from "@/config/fonts";
import { cn } from "@/lib/utils";
import {
convertToYaml,
defaultMutedFindingsConfig,
@@ -132,6 +138,13 @@ export function AdvancedMutelistForm() {
}
};
const isConfigInvalid =
(!hasUserStartedTyping && !!state?.errors?.configuration) ||
!yamlValidation.isValid;
const configErrorMessage =
(!hasUserStartedTyping && state?.errors?.configuration) ||
(!yamlValidation.isValid ? yamlValidation.error : "");
if (isLoading) {
return (
<Card variant="base" className="p-6">
@@ -159,7 +172,7 @@ export function AdvancedMutelistForm() {
size="md"
>
<div className="flex flex-col gap-4">
<p className="text-default-600 text-sm">
<p className="text-text-neutral-secondary text-sm">
Are you sure you want to delete this configuration? This action
cannot be undone.
</p>
@@ -193,10 +206,10 @@ export function AdvancedMutelistForm() {
<div className="flex flex-col gap-4">
<div>
<h3 className="text-default-700 mb-2 text-lg font-semibold">
<h3 className="text-text-neutral-secondary mb-2 text-lg font-semibold">
Advanced Mutelist Configuration
</h3>
<ul className="text-default-600 mb-4 list-disc pl-5 text-sm">
<ul className="text-text-neutral-secondary mb-4 list-disc pl-5 text-sm">
<li>
<strong>
This Mutelist configuration will take effect on the next
@@ -227,7 +240,7 @@ export function AdvancedMutelistForm() {
<div className="flex flex-col gap-2">
<label
htmlFor="configuration"
className="text-default-700 text-sm font-medium"
className="text-text-neutral-secondary text-sm font-medium"
>
Mutelist Configuration (YAML)
</label>
@@ -236,29 +249,26 @@ export function AdvancedMutelistForm() {
id="configuration"
name="configuration"
placeholder={defaultMutedFindingsConfig}
variant="bordered"
value={configText}
onChange={(e) => handleConfigChange(e.target.value)}
minRows={20}
maxRows={20}
isInvalid={
(!hasUserStartedTyping && !!state?.errors?.configuration) ||
!yamlValidation.isValid
}
errorMessage={
(!hasUserStartedTyping && state?.errors?.configuration) ||
(!yamlValidation.isValid ? yamlValidation.error : "")
}
classNames={{
input: fontMono.className + " text-sm",
base: "min-h-[400px]",
errorMessage: "whitespace-pre-wrap",
}}
rows={20}
aria-invalid={isConfigInvalid}
className={cn(
fontMono.className,
"min-h-[400px] text-sm",
isConfigInvalid &&
"border-border-error focus:border-border-error focus:ring-border-error",
)}
/>
{isConfigInvalid && configErrorMessage && (
<FieldError className="my-1 px-1 whitespace-pre-wrap">
{configErrorMessage}
</FieldError>
)}
{yamlValidation.isValid &&
configText &&
hasUserStartedTyping && (
<div className="text-tiny text-success my-1 flex items-center px-1">
<div className="text-text-success-primary my-1 flex items-center px-1 text-xs">
<span>Valid YAML format</span>
</div>
)}
@@ -11,13 +11,7 @@ vi.mock("@/actions/mute-rules", () => ({
updateMuteRule: updateMuteRuleMock,
}));
vi.mock("@/components/ui", () => ({
useToast: () => ({
toast: toastMock,
}),
}));
vi.mock("@/components/ui/form", () => ({
vi.mock("@/components/shadcn/form", () => ({
FormButtons: ({
onCancel,
submitText = "Save",
@@ -38,7 +32,9 @@ vi.mock("@/components/ui/form", () => ({
),
}));
vi.mock("@/components/shadcn", () => ({
vi.mock("@/components/shadcn", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
useToast: () => ({ toast: toastMock }),
Input: ({
defaultValue,
...props
@@ -53,7 +49,7 @@ vi.mock("@/components/shadcn", () => ({
),
}));
vi.mock("@/components/ui/form/Label", () => ({
vi.mock("@/components/shadcn/form/Label", () => ({
Label: ({
children,
...props
@@ -5,8 +5,8 @@ import { FormEvent, useState } from "react";
import { updateMuteRule } from "@/actions/mute-rules";
import { MuteRuleActionState, MuteRuleData } from "@/actions/mute-rules/types";
import { Input, Textarea } from "@/components/shadcn";
import { FormButtons } from "@/components/ui/form";
import { Label } from "@/components/ui/form/Label";
import { FormButtons } from "@/components/shadcn/form";
import { Label } from "@/components/shadcn/form/Label";
import { useMuteRuleAction } from "@/hooks/use-mute-rule-action";
import {
enforceMuteRuleReasonLimit,
@@ -1,11 +1,11 @@
"use client";
import { Switch } from "@heroui/switch";
import { useState } from "react";
import { toggleMuteRule } from "@/actions/mute-rules";
import { MuteRuleData } from "@/actions/mute-rules/types";
import { useToast } from "@/components/ui";
import { Switch } from "@/components/shadcn";
import { useToast } from "@/components/shadcn";
interface MuteRuleEnabledToggleProps {
muteRule: MuteRuleData;
@@ -44,10 +44,9 @@ export function MuteRuleEnabledToggle({
return (
<Switch
isSelected={isEnabled}
onValueChange={handleToggle}
isDisabled={isLoading}
size="sm"
checked={isEnabled}
onCheckedChange={handleToggle}
disabled={isLoading}
aria-label={`Toggle mute rule ${muteRule.attributes.name}`}
/>
);
@@ -4,11 +4,11 @@ import { describe, expect, it, vi } from "vitest";
import { createMuteRulesColumns } from "./mute-rules-columns";
vi.mock("@/components/ui/entities", () => ({
vi.mock("@/components/shadcn/entities", () => ({
DateWithTime: () => null,
}));
vi.mock("@/components/ui/table", () => ({
vi.mock("@/components/shadcn/table", () => ({
DataTableColumnHeader: ({ title }: { title: string }) => <span>{title}</span>,
}));
@@ -5,8 +5,8 @@ import { List } from "lucide-react";
import { MuteRuleData } from "@/actions/mute-rules/types";
import { Button } from "@/components/shadcn";
import { DateWithTime } from "@/components/ui/entities";
import { DataTableColumnHeader } from "@/components/ui/table";
import { DateWithTime } from "@/components/shadcn/entities";
import { DataTableColumnHeader } from "@/components/shadcn/table";
import { MuteRuleEnabledToggle } from "./mute-rule-enabled-toggle";
import { MuteRuleRowActions } from "./mute-rule-row-actions";
@@ -19,13 +19,9 @@ vi.mock("@/actions/mute-rules", () => ({
deleteMuteRule: deleteMuteRuleMock,
}));
vi.mock("@/components/ui", () => ({
useToast: () => ({
toast: toastMock,
}),
}));
vi.mock("@/components/shadcn", () => ({
vi.mock("@/components/shadcn", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
useToast: () => ({ toast: toastMock }),
Button: ({
children,
...props
@@ -52,7 +48,7 @@ vi.mock("@/components/shadcn/modal", () => ({
) : null,
}));
vi.mock("@/components/ui/table", () => ({
vi.mock("@/components/shadcn/table", () => ({
DataTable: (props: {
columns: Array<{
id?: string;
@@ -1,6 +1,5 @@
"use client";
import { useDisclosure } from "@heroui/use-disclosure";
import { Trash2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { FormEvent, useState } from "react";
@@ -8,9 +7,9 @@ import { FormEvent, useState } from "react";
import { deleteMuteRule } from "@/actions/mute-rules";
import { MuteRuleData } from "@/actions/mute-rules/types";
import { CardTitle } from "@/components/shadcn";
import { FormButtons } from "@/components/shadcn/form";
import { Modal } from "@/components/shadcn/modal";
import { FormButtons } from "@/components/ui/form";
import { DataTable } from "@/components/ui/table";
import { DataTable } from "@/components/shadcn/table";
import { useMuteRuleAction } from "@/hooks/use-mute-rule-action";
import { MetaDataProps } from "@/types";
@@ -37,27 +36,27 @@ export function MuteRulesTableClient({
const { isPending: isDeleting, runAction: runDeleteAction } =
useMuteRuleAction();
const editModal = useDisclosure();
const deleteModal = useDisclosure();
const targetsModal = useDisclosure();
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isTargetsModalOpen, setIsTargetsModalOpen] = useState(false);
const handleEditClick = (muteRule: MuteRuleData) => {
setSelectedMuteRule(muteRule);
editModal.onOpen();
setIsEditModalOpen(true);
};
const handleDeleteClick = (muteRule: MuteRuleData) => {
setSelectedMuteRule(muteRule);
deleteModal.onOpen();
setIsDeleteModalOpen(true);
};
const handleViewTargets = (muteRule: MuteRuleTableData) => {
setSelectedTargetsRule(muteRule);
targetsModal.onOpen();
setIsTargetsModalOpen(true);
};
const handleEditSuccess = () => {
editModal.onClose();
setIsEditModalOpen(false);
router.refresh();
};
@@ -68,7 +67,7 @@ export function MuteRulesTableClient({
runDeleteAction(() => deleteMuteRule(null, formData), {
onSuccess: () => {
deleteModal.onClose();
setIsDeleteModalOpen(false);
router.refresh();
},
});
@@ -102,15 +101,15 @@ export function MuteRulesTableClient({
<MuteRuleTargetsModal
muteRule={selectedTargetsRule}
open={targetsModal.isOpen}
onOpenChange={targetsModal.onOpenChange}
open={isTargetsModalOpen}
onOpenChange={setIsTargetsModalOpen}
/>
{/* Edit Modal */}
{selectedMuteRule && (
<Modal
open={editModal.isOpen}
onOpenChange={editModal.onOpenChange}
open={isEditModalOpen}
onOpenChange={setIsEditModalOpen}
title="Edit Mute Rule"
description="Update the rule metadata without changing the muted findings linked to it."
size="lg"
@@ -119,7 +118,7 @@ export function MuteRulesTableClient({
key={selectedMuteRule.id}
muteRule={selectedMuteRule}
onSuccess={handleEditSuccess}
onCancel={editModal.onClose}
onCancel={() => setIsEditModalOpen(false)}
/>
</Modal>
)}
@@ -127,8 +126,8 @@ export function MuteRulesTableClient({
{/* Delete Confirmation Modal */}
{selectedMuteRule && (
<Modal
open={deleteModal.isOpen}
onOpenChange={deleteModal.onOpenChange}
open={isDeleteModalOpen}
onOpenChange={setIsDeleteModalOpen}
title="Delete Mute Rule"
description="Remove this rule from Mutelist. Existing muted findings will remain muted."
size="md"
@@ -153,7 +152,7 @@ export function MuteRulesTableClient({
</div>
<FormButtons
onCancel={deleteModal.onClose}
onCancel={() => setIsDeleteModalOpen(false)}
submitText={isDeleting ? "Deleting..." : "Delete"}
isDisabled={isDeleting}
submitColor="danger"
@@ -94,7 +94,7 @@ export function MuteRulesTableSkeleton() {
return (
<div
data-testid="mute-rules-table-skeleton"
className="rounded-large shadow-small border-border-neutral-secondary bg-bg-neutral-secondary flex w-full flex-col gap-4 overflow-hidden border p-4"
className="border-border-neutral-secondary bg-bg-neutral-secondary flex w-full flex-col gap-4 overflow-hidden rounded-[14px] border p-4 shadow-sm"
>
<div
data-testid="mute-rules-table-skeleton-intro"
+1 -1
View File
@@ -1,6 +1,6 @@
import { Suspense } from "react";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { SearchParamsProps } from "@/types/components";
import { MuteRulesTable, MuteRulesTableSkeleton } from "./_components/simple";
+1 -1
View File
@@ -2,7 +2,7 @@ import { Suspense } from "react";
import { getAllProviders } from "@/actions/providers";
import { ProviderAccountSelectors } from "@/components/filters/provider-account-selectors";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { SearchParamsProps } from "@/types";
import {
+1 -1
View File
@@ -4,7 +4,7 @@ import { getSamlConfig } from "@/actions/integrations/saml";
import { getUserInfo } from "@/actions/users/users";
import { auth } from "@/auth.config";
import { SamlIntegrationCard } from "@/components/integrations/saml/saml-integration-card";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { ApiKeysCard, UserBasicInfoCard } from "@/components/users/profile";
import { MembershipsCard } from "@/components/users/profile/memberships-card";
import { RolesCard } from "@/components/users/profile/roles-card";
+1 -1
View File
@@ -3,8 +3,8 @@ import { Suspense } from "react";
import { ProvidersAccountsView } from "@/components/providers";
import { SkeletonTableProviders } from "@/components/providers/table";
import { CliImportBanner } from "@/components/scans";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { Skeleton } from "@/components/shadcn/skeleton/skeleton";
import { ContentLayout } from "@/components/ui";
import { FilterTransitionWrapper } from "@/contexts";
import { SearchParamsProps } from "@/types";
@@ -6,7 +6,7 @@ import { getAllProviders } from "@/actions/providers";
import { getRoles } from "@/actions/roles";
import { AddGroupForm, EditGroupForm } from "@/components/manage-groups/forms";
import { ColumnGroups } from "@/components/manage-groups/table";
import { DataTable } from "@/components/ui/table";
import { DataTable } from "@/components/shadcn/table";
import { ProviderProps, Role, SearchParamsProps } from "@/types";
export const ProviderGroupsContent = async ({
+2 -2
View File
@@ -11,7 +11,7 @@ import {
import { ResourcesFilters } from "@/components/resources/resources-filters";
import { SkeletonTableResources } from "@/components/resources/skeleton/skeleton-table-resources";
import { ResourcesTableWithSelection } from "@/components/resources/table";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { FilterTransitionWrapper } from "@/contexts";
import {
createDict,
@@ -166,7 +166,7 @@ const SSRDataTable = async ({
return (
<>
{resourcesData?.errors && (
<div className="text-small mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-red-700">
<div className="mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-sm text-red-700">
<p className="mr-2 font-semibold">Error:</p>
<p>{resourcesData.errors[0].detail}</p>
</div>
+2 -3
View File
@@ -1,10 +1,9 @@
import "@/styles/globals.css";
import { Spacer } from "@heroui/spacer";
import React from "react";
import { WorkflowAddEditRole } from "@/components/roles/workflow";
import { NavigationHeader } from "@/components/ui";
import { NavigationHeader } from "@/components/shadcn";
interface RoleLayoutProps {
children: React.ReactNode;
@@ -18,7 +17,7 @@ export default function RoleLayout({ children }: RoleLayoutProps) {
icon="icon-park-outline:close-small"
href="/roles"
/>
<Spacer y={16} />
<div className="h-16" />
<div className="grid grid-cols-1 gap-8 px-4 sm:px-6 lg:grid-cols-12 lg:px-0">
<div className="order-1 my-auto hidden h-full lg:col-span-4 lg:col-start-2 lg:block">
<WorkflowAddEditRole />
+2 -2
View File
@@ -7,8 +7,8 @@ import { filterRoles } from "@/components/filters/data-filters";
import { AddIcon } from "@/components/icons";
import { ColumnsRoles, SkeletonTableRoles } from "@/components/roles/table";
import { Button } from "@/components/shadcn";
import { ContentLayout } from "@/components/ui";
import { DataTable, DataTableFilterCustom } from "@/components/ui/table";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { DataTable, DataTableFilterCustom } from "@/components/shadcn/table";
import { SearchParamsProps } from "@/types";
export default async function Roles({
+1 -1
View File
@@ -12,7 +12,7 @@ import { ScansPageShell } from "@/components/scans/scans-page-shell";
import { ScansProvidersEmptyState } from "@/components/scans/scans-providers-empty-state";
import { SkeletonTableScans } from "@/components/scans/table";
import { ScanJobsTable } from "@/components/scans/table/scan-jobs-table";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
import {
ProviderProps,
SCAN_JOBS_TAB,
+3 -5
View File
@@ -1,7 +1,5 @@
import { Spacer } from "@heroui/spacer";
import { FilterControls } from "@/components/filters";
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
export default async function Services() {
// const searchParamsKey = JSON.stringify(searchParams || {});
@@ -10,9 +8,9 @@ export default async function Services() {
title="Services"
icon="material-symbols:linked-services-outline"
>
<Spacer y={4} />
<div className="h-4" />
<FilterControls />
<Spacer y={4} />
<div className="h-4" />
{/* <Suspense key={searchParamsKey} fallback={<ServiceSkeletonGrid />}>
<SSRServiceGrid />
</Suspense> */}
+2 -2
View File
@@ -7,8 +7,8 @@ import { auth } from "@/auth.config";
import { FilterControls } from "@/components/filters";
import { AddIcon } from "@/components/icons";
import { Button } from "@/components/shadcn";
import { ContentLayout } from "@/components/ui";
import { DataTable } from "@/components/ui/table";
import { ContentLayout } from "@/components/shadcn/content-layout";
import { DataTable } from "@/components/shadcn/table";
import { ColumnsUser, SkeletonTableUser } from "@/components/users/table";
import { Role, SearchParamsProps, UserProps } from "@/types";
import { TENANT_MEMBERSHIP_ROLE } from "@/types/users";
+1 -1
View File
@@ -1,4 +1,4 @@
import { ContentLayout } from "@/components/ui";
import { ContentLayout } from "@/components/shadcn/content-layout";
export default async function Workloads() {
return (
+3 -9
View File
@@ -3,26 +3,20 @@
// Import Sentry client-side initialization
import "@/app/instrumentation.client";
import { HeroUIProvider } from "@heroui/system";
import { useRouter } from "next/navigation";
import { SessionProvider } from "next-auth/react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { ThemeProviderProps } from "next-themes/dist/types";
import * as React from "react";
import { ReactNode } from "react";
export interface ProvidersProps {
children: React.ReactNode;
children: ReactNode;
themeProps?: ThemeProviderProps;
}
export function Providers({ children, themeProps }: ProvidersProps) {
const router = useRouter();
return (
<SessionProvider>
<HeroUIProvider navigate={router.push}>
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
</HeroUIProvider>
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
</SessionProvider>
);
}
+32 -71
View File
@@ -1,96 +1,57 @@
"use client";
import type { SwitchProps } from "@heroui/switch";
import { useSwitch } from "@heroui/switch";
import { useIsSSR } from "@react-aria/ssr";
import { VisuallyHidden } from "@react-aria/visually-hidden";
import clsx from "clsx";
import { useTheme } from "next-themes";
import { FC } from "react";
import React from "react";
import { ComponentProps, useSyncExternalStore } from "react";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/shadcn/tooltip";
import { cn } from "@/lib/utils";
import { MoonFilledIcon, SunFilledIcon } from "./icons";
export interface ThemeSwitchProps {
className?: string;
classNames?: SwitchProps["classNames"];
}
export type ThemeSwitchProps = ComponentProps<"button">;
export const ThemeSwitch: FC<ThemeSwitchProps> = ({
className,
classNames,
}) => {
const emptySubscribe = () => () => {};
export function ThemeSwitch({ className, ...props }: ThemeSwitchProps) {
const { theme, setTheme } = useTheme();
const isSSR = useIsSSR();
// Hydration-safe mounted check: false on the server, true after hydration
const isHydrated = useSyncExternalStore(
emptySubscribe,
() => true,
() => false,
);
const onChange = () => {
theme === "light" ? setTheme("dark") : setTheme("light");
};
const {
Component,
slots,
isSelected,
getBaseProps,
getInputProps,
getWrapperProps,
} = useSwitch({
isSelected: theme === "light" || isSSR,
"aria-label": `Switch to ${theme === "light" || isSSR ? "dark" : "light"} mode`,
onChange,
});
const isLightMode = theme === "light" || !isHydrated;
return (
<Tooltip>
<TooltipTrigger asChild>
<Component
{...getBaseProps({
className: clsx(
"px-px transition-opacity hover:opacity-80 cursor-pointer",
className,
classNames?.base,
),
})}
<button
type="button"
{...props}
role="switch"
aria-checked={isLightMode}
aria-label={`Switch to ${isLightMode ? "dark" : "light"} mode`}
onClick={() => setTheme(isLightMode ? "dark" : "light")}
className={cn(
"text-neutral-tertiary flex cursor-pointer items-center justify-center rounded-lg px-px pt-px transition-opacity hover:opacity-80",
className,
)}
>
<VisuallyHidden>
<input {...getInputProps()} />
</VisuallyHidden>
<div
{...getWrapperProps()}
className={slots.wrapper({
class: clsx(
[
"h-auto w-auto",
"bg-transparent",
"rounded-lg",
"flex items-center justify-center",
"group-data-[selected=true]:bg-transparent",
"!text-default-500",
"pt-px",
"px-0",
"mx-0",
],
classNames?.wrapper,
),
})}
>
{!isSelected || isSSR ? (
<SunFilledIcon size={22} />
) : (
<MoonFilledIcon size={22} />
)}
</div>
</Component>
{isLightMode && isHydrated ? (
<MoonFilledIcon size={22} />
) : (
<SunFilledIcon size={22} />
)}
</button>
</TooltipTrigger>
<TooltipContent>
{isSelected || isSSR ? "Switch to Dark Mode" : "Switch to Light Mode"}
{isLightMode ? "Switch to Dark Mode" : "Switch to Light Mode"}
</TooltipContent>
</Tooltip>
);
};
}
+4 -4
View File
@@ -1,11 +1,11 @@
import { Divider } from "@heroui/divider";
import { Separator } from "@/components/shadcn";
export const AuthDivider = () => {
return (
<div className="flex items-center gap-4 py-2">
<Divider className="flex-1" />
<p className="text-tiny text-default-500 shrink-0">OR</p>
<Divider className="flex-1" />
<Separator className="flex-1" />
<p className="text-text-neutral-tertiary shrink-0 text-xs">OR</p>
<Separator className="flex-1" />
</div>
);
};
+2 -2
View File
@@ -1,4 +1,4 @@
import { CustomLink } from "@/components/ui/custom/custom-link";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
interface AuthFooterLinkProps {
text: string;
@@ -12,7 +12,7 @@ export const AuthFooterLink = ({
href,
}: AuthFooterLinkProps) => {
return (
<p className="text-small text-center">
<p className="text-center text-sm">
{text}&nbsp;
<CustomLink size="md" href={href} target="_self">
{linkText}
+1 -1
View File
@@ -27,7 +27,7 @@ export const AuthLayout = ({ title, children }: AuthLayoutProps) => {
</div>
{/* Auth Form Container */}
<div className="rounded-large border-divider shadow-small dark:bg-background/85 relative z-10 flex w-full max-w-sm flex-col gap-4 border bg-white/90 px-8 py-10 md:max-w-md">
<div className="border-border-neutral-secondary dark:bg-bg-neutral-primary/85 relative z-10 flex w-full max-w-sm flex-col gap-4 rounded-[14px] border bg-white/90 px-8 py-10 shadow-sm md:max-w-md">
{/* Header with Title and Theme Toggle */}
<div className="flex items-center justify-between">
<p className="pb-2 text-xl font-medium">{title}</p>
+4 -4
View File
@@ -13,9 +13,9 @@ import { AuthFooterLink } from "@/components/auth/oss/auth-footer-link";
import { AuthLayout } from "@/components/auth/oss/auth-layout";
import { SocialButtons } from "@/components/auth/oss/social-buttons";
import { Button } from "@/components/shadcn";
import { useToast } from "@/components/ui";
import { CustomInput } from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import { useToast } from "@/components/shadcn";
import { CustomInput } from "@/components/shadcn/custom";
import { Form } from "@/components/shadcn/form";
import { SignInFormData, signInSchema } from "@/types";
export const SignInForm = ({
@@ -194,7 +194,7 @@ export const SignInForm = ({
>
{!isSamlMode && (
<Icon
className="text-default-500"
className="text-text-neutral-tertiary"
icon="mdi:shield-key"
width={24}
/>
+20 -16
View File
@@ -1,6 +1,5 @@
"use client";
import { Checkbox } from "@heroui/checkbox";
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter } from "next/navigation";
import { useForm, useWatch } from "react-hook-form";
@@ -16,16 +15,16 @@ import { AuthFooterLink } from "@/components/auth/oss/auth-footer-link";
import { AuthLayout } from "@/components/auth/oss/auth-layout";
import { PasswordRequirementsMessage } from "@/components/auth/oss/password-validator";
import { SocialButtons } from "@/components/auth/oss/social-buttons";
import { Button } from "@/components/shadcn";
import { useToast } from "@/components/ui";
import { CustomInput } from "@/components/ui/custom";
import { CustomLink } from "@/components/ui/custom/custom-link";
import { Button, Checkbox } from "@/components/shadcn";
import { useToast } from "@/components/shadcn";
import { CustomInput } from "@/components/shadcn/custom";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
import {
Form,
FormControl,
FormField,
FormMessage,
} from "@/components/ui/form";
} from "@/components/shadcn/form";
import { ApiError, SignUpFormData, signUpSchema } from "@/types";
const AUTH_ERROR_PATHS = {
@@ -206,14 +205,18 @@ export const SignUpForm = ({
name="termsAndConditions"
render={({ field }) => (
<>
<FormControl>
<Checkbox
isRequired
className="py-4"
size="sm"
checked={field.value}
onChange={(e) => field.onChange(e.target.checked)}
color="default"
<div className="flex items-center gap-2 py-4">
<FormControl>
<Checkbox
id="termsAndConditions"
size="sm"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<label
htmlFor="termsAndConditions"
className="cursor-pointer text-sm"
>
I agree with the&nbsp;
<CustomLink
@@ -223,8 +226,9 @@ export const SignUpForm = ({
Terms of Service
</CustomLink>
&nbsp;of Prowler
</Checkbox>
</FormControl>
<span className="text-text-error-primary">*</span>
</label>
</div>
<FormMessage className="text-text-error" />
</>
)}
+82 -70
View File
@@ -1,8 +1,12 @@
import { Tooltip } from "@heroui/tooltip";
import { Icon } from "@iconify/react";
import { Button } from "@/components/shadcn";
import { CustomLink } from "@/components/ui/custom/custom-link";
import {
Button,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/shadcn";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
export const SocialButtons = ({
googleAuthUrl,
@@ -14,70 +18,78 @@ export const SocialButtons = ({
githubAuthUrl?: string;
isGoogleOAuthEnabled?: boolean;
isGithubOAuthEnabled?: boolean;
}) => (
<>
<Tooltip
content={
<div className="flex-inline text-small">
Social Login with Google is not enabled.{" "}
<CustomLink href="https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app-social-login/#google-oauth-configuration">
Read the docs
</CustomLink>
</div>
}
placement="top"
shadow="sm"
isDisabled={isGoogleOAuthEnabled}
className="w-96"
>
<span>
<Button
variant="outline"
className="w-full"
asChild={isGoogleOAuthEnabled}
disabled={!isGoogleOAuthEnabled}
>
<a href={googleAuthUrl} className="flex items-center gap-2">
<Icon
icon={
isGoogleOAuthEnabled
? "flat-color-icons:google"
: "simple-icons:google"
}
width={24}
/>
Continue with Google
</a>
</Button>
</span>
</Tooltip>
<Tooltip
content={
<div className="flex-inline text-small">
Social Login with Github is not enabled.{" "}
<CustomLink href="https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app-social-login/#github-oauth-configuration">
Read the docs
</CustomLink>
</div>
}
placement="top"
shadow="sm"
isDisabled={isGithubOAuthEnabled}
className="w-96"
>
<span>
<Button
variant="outline"
className="w-full"
asChild={isGithubOAuthEnabled}
disabled={!isGithubOAuthEnabled}
>
<a href={githubAuthUrl} className="flex items-center gap-2">
<Icon icon="simple-icons:github" width={24} />
Continue with Github
</a>
</Button>
</span>
</Tooltip>
</>
);
}) => {
const googleButton = (
<span>
<Button
variant="outline"
className="w-full"
asChild={isGoogleOAuthEnabled}
disabled={!isGoogleOAuthEnabled}
>
<a href={googleAuthUrl} className="flex items-center gap-2">
<Icon
icon={
isGoogleOAuthEnabled
? "flat-color-icons:google"
: "simple-icons:google"
}
width={24}
/>
Continue with Google
</a>
</Button>
</span>
);
const githubButton = (
<span>
<Button
variant="outline"
className="w-full"
asChild={isGithubOAuthEnabled}
disabled={!isGithubOAuthEnabled}
>
<a href={githubAuthUrl} className="flex items-center gap-2">
<Icon icon="simple-icons:github" width={24} />
Continue with Github
</a>
</Button>
</span>
);
return (
<>
{isGoogleOAuthEnabled ? (
googleButton
) : (
<Tooltip>
<TooltipTrigger asChild>{googleButton}</TooltipTrigger>
<TooltipContent side="top" className="w-96">
<div className="flex-inline text-sm">
Social Login with Google is not enabled.{" "}
<CustomLink href="https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app-social-login/#google-oauth-configuration">
Read the docs
</CustomLink>
</div>
</TooltipContent>
</Tooltip>
)}
{isGithubOAuthEnabled ? (
githubButton
) : (
<Tooltip>
<TooltipTrigger asChild>{githubButton}</TooltipTrigger>
<TooltipContent side="top" className="w-96">
<div className="flex-inline text-sm">
Social Login with Github is not enabled.{" "}
<CustomLink href="https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app-social-login/#github-oauth-configuration">
Read the docs
</CustomLink>
</div>
</TooltipContent>
</Tooltip>
)}
</>
);
};
@@ -8,8 +8,8 @@ import {
getStandaloneFindingColumns,
SkeletonTableFindings,
} from "@/components/findings/table";
import { Accordion } from "@/components/ui/accordion/Accordion";
import { DataTable } from "@/components/ui/table";
import { Accordion } from "@/components/shadcn/accordion/Accordion";
import { DataTable } from "@/components/shadcn/table";
import { createDict, FINDINGS_DEFAULT_SORT, MUTED_FILTER } from "@/lib";
import { getComplianceMapper } from "@/lib/compliance/compliance-mapper";
import { Requirement } from "@/types/compliance";
@@ -162,7 +162,7 @@ export const ClientAccordionContent = ({
key: "checks",
title: (
<div className="flex items-center gap-2">
<span className="text-primary">{checks.length}</span>
<span className="text-button-primary">{checks.length}</span>
{checks.length > 1 ? <span>Checks</span> : <span>Check</span>}
</div>
),
@@ -207,7 +207,7 @@ export const ClientAccordionContent = ({
items={accordionChecksItems}
variant="light"
defaultExpandedKeys={[""]}
className="dark:bg-prowler-blue-400 rounded-lg bg-gray-50"
className="dark:bg-bg-neutral-secondary rounded-lg bg-gray-50"
/>
</div>
)}
@@ -3,7 +3,7 @@
import { useRef, useState } from "react";
import { Button } from "@/components/shadcn";
import { Accordion, AccordionItemProps } from "@/components/ui";
import { Accordion, AccordionItemProps } from "@/components/shadcn";
export const ClientAccordionWrapper = ({
items,
@@ -71,7 +71,7 @@ export const ClientAccordionWrapper = ({
lastScrolledKeyRef.current = scrollToKey;
// Two nested rAFs: the first lets the accordion children commit to
// the DOM, the second lands after the browser has run a layout pass
// so HeroUI's framer-motion expand has settled enough for
// so the Collapsible CSS expand animation has settled enough for
// scrollIntoView to read a stable offset.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
@@ -1,4 +1,4 @@
import { FindingStatus, StatusFindingBadge } from "@/components/ui/table";
import { FindingStatus, StatusFindingBadge } from "@/components/shadcn/table";
interface ComplianceAccordionRequirementTitleProps {
type: string;
@@ -15,7 +15,7 @@ export const ComplianceAccordionRequirementTitle = ({
<div className="flex w-full items-center justify-between gap-2">
<div className="flex w-5/6 items-center gap-2">
{type && (
<span className="bg-primary/10 text-primary rounded-md px-2 py-0.5 text-xs font-medium">
<span className="bg-button-primary/10 text-button-primary rounded-md px-2 py-0.5 text-xs font-medium">
{type}
</span>
)}
@@ -1,4 +1,4 @@
import { Tooltip } from "@heroui/tooltip";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/shadcn";
interface ComplianceAccordionTitleProps {
label: string;
@@ -43,72 +43,63 @@ export const ComplianceAccordionTitle = ({
{total > 0 ? (
<div className="flex w-full">
{pass > 0 && (
<Tooltip
content={
<Tooltip>
<TooltipTrigger asChild>
<span
className="inline-block h-full bg-[#3CEC6D] transition-all duration-200 hover:brightness-110"
style={{
width: `${passPercentage}%`,
marginRight: pass > 0 ? "2px" : "0",
}}
/>
</TooltipTrigger>
<TooltipContent side="top">
<div className="px-1 py-0.5">
<div className="text-xs font-medium">Pass</div>
<div className="text-tiny text-default-400">
<div className="text-text-neutral-tertiary text-xs">
{pass} ({passPercentage.toFixed(1)}%)
</div>
</div>
}
size="sm"
placement="top"
delay={0}
closeDelay={0}
>
<span
className="inline-block h-full bg-[#3CEC6D] transition-all duration-200 hover:brightness-110"
style={{
width: `${passPercentage}%`,
marginRight: pass > 0 ? "2px" : "0",
}}
/>
</TooltipContent>
</Tooltip>
)}
{fail > 0 && (
<Tooltip
content={
<Tooltip>
<TooltipTrigger asChild>
<span
className="inline-block h-full bg-[#FB718F] transition-all duration-200 hover:brightness-110"
style={{
width: `${failPercentage}%`,
marginRight: manual > 0 ? "2px" : "0",
}}
/>
</TooltipTrigger>
<TooltipContent side="top">
<div className="px-1 py-0.5">
<div className="text-xs font-medium">Fail</div>
<div className="text-tiny text-default-400">
<div className="text-text-neutral-tertiary text-xs">
{fail} ({failPercentage.toFixed(1)}%)
</div>
</div>
}
size="sm"
placement="top"
delay={0}
closeDelay={0}
>
<span
className="inline-block h-full bg-[#FB718F] transition-all duration-200 hover:brightness-110"
style={{
width: `${failPercentage}%`,
marginRight: manual > 0 ? "2px" : "0",
}}
/>
</TooltipContent>
</Tooltip>
)}
{manual > 0 && (
<Tooltip
content={
<Tooltip>
<TooltipTrigger asChild>
<span
className="inline-block h-full bg-[#868994] transition-all duration-200 hover:brightness-110"
style={{ width: `${manualPercentage}%` }}
/>
</TooltipTrigger>
<TooltipContent side="top">
<div className="px-1 py-0.5">
<div className="text-xs font-medium">Manual</div>
<div className="text-tiny text-default-400">
<div className="text-text-neutral-tertiary text-xs">
{manual} ({manualPercentage.toFixed(1)}%)
</div>
</div>
}
size="sm"
placement="top"
delay={0}
closeDelay={0}
>
<span
className="inline-block h-full bg-[#868994] transition-all duration-200 hover:brightness-110"
style={{ width: `${manualPercentage}%` }}
/>
</TooltipContent>
</Tooltip>
)}
</div>
@@ -117,19 +108,18 @@ export const ComplianceAccordionTitle = ({
)}
</div>
<Tooltip
content={
<Tooltip>
<TooltipTrigger asChild>
<span className="text-text-neutral-secondary min-w-[32px] text-center text-xs font-medium">
{total > 0 ? total : "—"}
</span>
</TooltipTrigger>
<TooltipContent side="top">
<div className="px-1 py-0.5">
<div className="text-xs font-medium">Total requirements</div>
<div className="text-tiny text-default-400">{total}</div>
<div className="text-text-neutral-tertiary text-xs">{total}</div>
</div>
}
size="sm"
placement="top"
>
<span className="text-default-600 min-w-[32px] text-center text-xs font-medium">
{total > 0 ? total : "—"}
</span>
</TooltipContent>
</Tooltip>
</div>
</div>
+1 -1
View File
@@ -135,7 +135,7 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
<div className="flex min-w-0 flex-1 flex-col">
<Tooltip>
<TooltipTrigger asChild>
<h4 className="text-small truncate leading-5 font-bold">
<h4 className="truncate text-sm leading-5 font-bold">
{formatTitle(title)}
{version ? ` - ${version}` : ""}
</h4>
@@ -1,9 +1,9 @@
"use client";
import { cn } from "@heroui/theme";
import { useTheme } from "next-themes";
import { useState } from "react";
import { cn } from "@/lib/utils";
import { CategoryData } from "@/types/compliance";
interface HeatmapChartProps {
@@ -1,12 +1,12 @@
"use client";
import { Progress } from "@heroui/progress";
import type { SectionScores } from "@/actions/overview/threat-score";
import { RadialChart } from "@/components/graphs/radial-chart";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/shadcn";
import { Progress } from "@/components/shadcn/progress";
import {
getScoreColor,
getScoreIndicatorClass,
getScoreLabel,
getScoreLevel,
getScoreTextClass,
@@ -72,7 +72,7 @@ export function ThreatScoreBreakdownCard({
{/* Pillar Breakdown */}
<Card variant="inner" padding="sm" className="min-w-0 flex-1">
<div className="mb-2">
<span className="text-default-600 text-xs font-medium tracking-wide uppercase">
<span className="text-text-neutral-secondary text-xs font-medium tracking-wide uppercase">
Score by Pillar
</span>
</div>
@@ -80,7 +80,9 @@ export function ThreatScoreBreakdownCard({
{pillars.map(({ name, score, hasData }) => (
<div key={name} className="space-y-0.5">
<div className="flex items-center justify-between text-xs">
<span className="text-default-700 truncate pr-2">{name}</span>
<span className="text-text-neutral-secondary truncate pr-2">
{name}
</span>
<span
className={`font-semibold ${
hasData
@@ -94,8 +96,9 @@ export function ThreatScoreBreakdownCard({
<Progress
aria-label={`${name} score`}
value={hasData ? score : 0}
color={getScoreColor(score)}
size="md"
indicatorClassName={getScoreIndicatorClass(
getScoreColor(score),
)}
className="w-full"
/>
</div>
@@ -113,22 +116,22 @@ export function ThreatScoreBreakdownCardSkeleton() {
return (
<Card variant="base" className="flex h-full w-full animate-pulse flex-col">
<CardHeader>
<div className="bg-default-200 h-5 w-40 rounded" />
<div className="bg-border-neutral-tertiary h-5 w-40 rounded" />
</CardHeader>
{/* Mobile: vertical, Tablet: horizontal, Desktop: vertical */}
<CardContent className="flex flex-1 flex-col gap-4 md:flex-row md:items-stretch lg:flex-col">
<div className="flex w-full flex-col items-center justify-center md:w-[160px] md:flex-shrink-0 lg:w-full">
<div className="bg-default-200 mx-auto h-[140px] w-[140px] rounded-full" />
<div className="bg-border-neutral-tertiary mx-auto h-[140px] w-[140px] rounded-full" />
</div>
<Card variant="inner" padding="sm" className="min-w-0 flex-1">
<div className="space-y-2">
{Array.from({ length: SKELETON_PILLAR_COUNT }, (_, i) => (
<div key={i} className="space-y-0.5">
<div className="flex justify-between">
<div className="bg-default-200 h-3 w-28 rounded" />
<div className="bg-default-200 h-3 w-10 rounded" />
<div className="bg-border-neutral-tertiary h-3 w-28 rounded" />
<div className="bg-border-neutral-tertiary h-3 w-10 rounded" />
</div>
<div className="bg-default-200 h-2 w-full rounded" />
<div className="bg-border-neutral-tertiary h-2 w-full rounded" />
</div>
))}
</div>
@@ -6,7 +6,7 @@ import { describe, expect, it, vi } from "vitest";
// `next-auth` (server-only). Stub it with a plain anchor — we only need
// the `<a>` semantics here so the regex/extraction tests can assert on
// `href` and accessible name.
vi.mock("@/components/ui/custom/custom-link", () => ({
vi.mock("@/components/shadcn/custom/custom-link", () => ({
CustomLink: ({ href, children }: { href: string; children: ReactNode }) => (
<a href={href}>{children}</a>
),
@@ -1,6 +1,6 @@
import ReactMarkdown from "react-markdown";
import { CustomLink } from "@/components/ui/custom/custom-link";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
import {
isASDAssessmentStatus,
isASDCloudApplicability,
@@ -1,5 +1,5 @@
import { CustomLink } from "@/components/ui/custom/custom-link";
import { SeverityBadge } from "@/components/ui/table";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
import { SeverityBadge } from "@/components/shadcn/table";
import { Requirement } from "@/types/compliance";
import {
@@ -1,6 +1,6 @@
import ReactMarkdown from "react-markdown";
import { CustomLink } from "@/components/ui/custom/custom-link";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
import { Requirement } from "@/types/compliance";
import {
@@ -1,4 +1,4 @@
import { CustomLink } from "@/components/ui/custom/custom-link";
import { CustomLink } from "@/components/shadcn/custom/custom-link";
import { Requirement } from "@/types/compliance";
import {
@@ -1,4 +1,4 @@
import { Severity, SeverityBadge } from "@/components/ui/table";
import { Severity, SeverityBadge } from "@/components/shadcn/table";
import { Requirement } from "@/types/compliance";
import {
@@ -22,7 +22,8 @@ vi.mock("@/lib/helper", () => ({
downloadCompliancePdf: downloadCompliancePdfMock,
}));
vi.mock("@/components/ui", () => ({
vi.mock("@/components/shadcn", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
toast: {},
}));
@@ -3,6 +3,7 @@
import { DownloadIcon, FileJsonIcon, FileTextIcon } from "lucide-react";
import { useState } from "react";
import { toast } from "@/components/shadcn";
import { Button } from "@/components/shadcn/button/button";
import {
ActionDropdown,
@@ -13,7 +14,6 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/shadcn/tooltip";
import { toast } from "@/components/ui";
import {
type ComplianceReportType,
isOcsfSupported,
@@ -2,7 +2,7 @@
import Image from "next/image";
import { DataTableFilterCustom } from "@/components/ui/table/data-table-filter-custom";
import { DataTableFilterCustom } from "@/components/shadcn/table/data-table-filter-custom";
import { ScanEntity } from "@/types/scans";
import { ComplianceScanInfo } from "./compliance-scan-info";
@@ -1,7 +1,10 @@
import { Divider } from "@heroui/divider";
import { Tooltip } from "@heroui/tooltip";
import { DateWithTime, EntityInfo } from "@/components/ui/entities";
import {
Separator,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/shadcn";
import { DateWithTime, EntityInfo } from "@/components/shadcn/entities";
import { ProviderType } from "@/types";
interface ComplianceScanInfoProps {
@@ -29,16 +32,17 @@ export const ComplianceScanInfo = ({ scan }: ComplianceScanInfoProps) => {
showCopyAction={false}
/>
</div>
<Divider orientation="vertical" className="h-8 shrink-0" />
<Separator orientation="vertical" className="h-8 shrink-0" />
<div className="flex min-w-0 basis-1/2 flex-col items-start overflow-hidden">
<Tooltip
content={scan.attributes.name || "- -"}
placement="top"
size="sm"
>
<p className="text-default-500 truncate text-xs">
<Tooltip>
<TooltipTrigger asChild>
<p className="text-text-neutral-tertiary truncate text-xs">
{scan.attributes.name || "- -"}
</p>
</TooltipTrigger>
<TooltipContent side="top">
{scan.attributes.name || "- -"}
</p>
</TooltipContent>
</Tooltip>
<DateWithTime inline dateTime={scan.attributes.completed_at} />
</div>
@@ -3,7 +3,7 @@
import { useState } from "react";
import { ComplianceCard } from "@/components/compliance/compliance-card";
import { DataTableSearch } from "@/components/ui/table/data-table-search";
import { DataTableSearch } from "@/components/shadcn/table/data-table-search";
import type { ComplianceOverviewData } from "@/types/compliance";
import type { ScanEntity } from "@/types/scans";
@@ -1,14 +1,12 @@
"use client";
import { Skeleton } from "@heroui/skeleton";
import { Skeleton } from "@/components/shadcn";
export const BarChartSkeleton = () => {
return (
<div className="flex w-[400px] flex-col items-center justify-between">
{/* Title skeleton */}
<Skeleton className="h-4 w-40 rounded-lg">
<div className="bg-default-200 h-4" />
</Skeleton>
<Skeleton className="h-4 w-40 rounded-lg" />
{/* Chart area skeleton */}
<div className="ml-24 flex h-full flex-col justify-center gap-2 p-4">
@@ -28,9 +26,7 @@ export const BarChartSkeleton = () => {
? "w-24"
: "w-16"
}`}
>
<div className="bg-default-200 h-6" />
</Skeleton>
/>
</div>
))}
@@ -38,12 +34,8 @@ export const BarChartSkeleton = () => {
<div className="flex justify-center gap-4 pt-2">
{Array.from({ length: 3 }).map((_, index) => (
<div key={index} className="flex items-center gap-1">
<Skeleton className="h-3 w-3 rounded-full">
<div className="bg-default-200 h-3 w-3" />
</Skeleton>
<Skeleton className="h-3 w-16 rounded-lg">
<div className="bg-default-200 h-3" />
</Skeleton>
<Skeleton className="h-3 w-3 rounded-full" />
<Skeleton className="h-3 w-16 rounded-lg" />
</div>
))}
</div>
@@ -1,5 +1,4 @@
import { Skeleton } from "@heroui/skeleton";
import React from "react";
import { Skeleton } from "@/components/shadcn";
interface SkeletonAccordionProps {
itemCount?: number;
@@ -19,9 +18,7 @@ export const SkeletonAccordion = ({
className={`flex w-full flex-col gap-2 ${className} rounded-xl border border-gray-300 p-2 dark:border-gray-700`}
>
{[...Array(itemCount)].map((_, index) => (
<Skeleton key={index} className="rounded-lg">
<div className={`${itemHeight} bg-default-300`}></div>
</Skeleton>
<Skeleton key={index} className={`${itemHeight} rounded-lg`} />
))}
</div>
);
@@ -1,25 +1,18 @@
"use client";
import { Skeleton } from "@heroui/skeleton";
import { Skeleton } from "@/components/shadcn";
export const HeatmapChartSkeleton = () => {
return (
<div className="flex h-[320px] w-[400px] flex-col items-center justify-between lg:w-[400px]">
{/* Title skeleton */}
<Skeleton className="h-4 w-36 rounded-lg">
<div className="bg-default-200 h-4" />
</Skeleton>
<Skeleton className="h-4 w-36 rounded-lg" />
{/* Heatmap area skeleton - 3x3 grid like the real component */}
<div className="h-full w-full p-4">
<div className="grid h-full w-full grid-cols-3 gap-1">
{Array.from({ length: 9 }).map((_, index) => (
<Skeleton
key={index}
className="flex items-center justify-center rounded border"
>
<div className="bg-default-200 h-full w-full" />
</Skeleton>
<Skeleton key={index} className="rounded border" />
))}
</div>
</div>
@@ -1,61 +1,41 @@
"use client";
import { Skeleton } from "@heroui/skeleton";
import { Skeleton } from "@/components/shadcn";
export const PieChartSkeleton = () => {
return (
<div className="flex h-[320px] flex-col items-center justify-between">
{/* Title skeleton */}
<Skeleton className="h-4 w-32 rounded-lg">
<div className="bg-default-200 h-4" />
</Skeleton>
<Skeleton className="h-4 w-32 rounded-lg" />
{/* Pie chart skeleton */}
<div className="relative flex aspect-square w-[200px] min-w-[200px] items-center justify-center">
{/* Outer circle */}
<Skeleton className="absolute h-[200px] w-[200px] rounded-full">
<div className="bg-default-200 h-[200px] w-[200px]" />
</Skeleton>
<Skeleton className="absolute h-[200px] w-[200px] rounded-full" />
{/* Inner circle (donut hole) */}
<div className="bg-background absolute h-[140px] w-[140px] rounded-full"></div>
<div className="bg-bg-neutral-primary absolute h-[140px] w-[140px] rounded-full"></div>
{/* Center text skeleton */}
<div className="absolute flex flex-col items-center">
<Skeleton className="h-6 w-8 rounded-lg">
<div className="bg-default-300 h-6" />
</Skeleton>
<Skeleton className="mt-1 h-3 w-6 rounded-lg">
<div className="bg-default-300 h-3" />
</Skeleton>
<Skeleton className="h-6 w-8 rounded-lg" />
<Skeleton className="mt-1 h-3 w-6 rounded-lg" />
</div>
</div>
{/* Bottom stats skeleton */}
<div className="mt-2 grid grid-cols-3 gap-4">
<div className="flex flex-col items-center">
<Skeleton className="h-4 w-8 rounded-lg">
<div className="bg-default-200 h-4" />
</Skeleton>
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
<div className="bg-default-200 h-5" />
</Skeleton>
<Skeleton className="h-4 w-8 rounded-lg" />
<Skeleton className="mt-1 h-5 w-6 rounded-lg" />
</div>
<div className="flex flex-col items-center">
<Skeleton className="h-4 w-6 rounded-lg">
<div className="bg-default-200 h-4" />
</Skeleton>
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
<div className="bg-default-200 h-5" />
</Skeleton>
<Skeleton className="h-4 w-6 rounded-lg" />
<Skeleton className="mt-1 h-5 w-6 rounded-lg" />
</div>
<div className="flex flex-col items-center">
<Skeleton className="h-4 w-12 rounded-lg">
<div className="bg-default-200 h-4" />
</Skeleton>
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
<div className="bg-default-200 h-5" />
</Skeleton>
<Skeleton className="h-4 w-12 rounded-lg" />
<Skeleton className="mt-1 h-5 w-6 rounded-lg" />
</div>
</div>
</div>
@@ -6,13 +6,13 @@ import { useState } from "react";
import type { SectionScores } from "@/actions/overview/threat-score";
import { ThreatScoreLogo } from "@/components/compliance/threatscore-logo";
import { toast } from "@/components/shadcn";
import { Card, CardContent } from "@/components/shadcn/card/card";
import {
ActionDropdown,
ActionDropdownItem,
} from "@/components/shadcn/dropdown";
import { Progress } from "@/components/shadcn/progress";
import { toast } from "@/components/ui";
import { COMPLIANCE_REPORT_TYPES } from "@/lib/compliance/compliance-report-types";
import {
getScoreColor,
@@ -8,7 +8,8 @@ vi.mock("lucide-react", () => ({
}));
// Mock @/components/shadcn to avoid next-auth import chain
vi.mock("@/components/shadcn", () => ({
vi.mock("@/components/shadcn", async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
Button: ({
children,
disabled,
+20 -50
View File
@@ -1,38 +1,31 @@
import { Input } from "@heroui/input";
import { SearchIcon, XCircle } from "lucide-react";
import { useSearchParams } from "next/navigation";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { SearchInput } from "@/components/shadcn";
import { useUrlFilters } from "@/hooks/use-url-filters";
export const CustomSearchInput: React.FC = () => {
export const CustomSearchInput = () => {
const searchParams = useSearchParams();
const { updateFilter } = useUrlFilters();
const [searchQuery, setSearchQuery] = useState("");
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const applySearch = useCallback(
(query: string) => {
if (query) {
updateFilter("search", query);
} else {
updateFilter("search", null);
}
},
[updateFilter],
);
const applySearch = (query: string) => {
if (query) {
updateFilter("search", query);
} else {
updateFilter("search", null);
}
};
const debouncedChangeHandler = useCallback(
(value: string) => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
debounceTimeoutRef.current = setTimeout(() => {
applySearch(value);
}, 300);
},
[applySearch],
);
const debouncedChangeHandler = (value: string) => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
debounceTimeoutRef.current = setTimeout(() => {
applySearch(value);
}, 300);
};
const clearIconSearch = () => {
setSearchQuery("");
@@ -53,39 +46,16 @@ export const CustomSearchInput: React.FC = () => {
}, []);
return (
<Input
style={{
borderRadius: "0.5rem",
}}
classNames={{
base: "w-full [&]:!rounded-lg [&>*]:!rounded-lg",
input:
"text-bg-button-secondary placeholder:text-bg-button-secondary text-sm",
inputWrapper:
"!border-border-input-primary !bg-bg-input-primary dark:!bg-input/30 dark:hover:!bg-input/50 hover:!bg-bg-neutral-secondary !border [&]:!rounded-lg !shadow-xs !transition-[color,box-shadow] focus-within:!border-border-input-primary-press focus-within:!ring-1 focus-within:!ring-border-input-primary-press focus-within:!ring-offset-1 !h-10 !px-4 !py-3 !outline-none",
clearButton: "text-bg-button-secondary",
}}
<SearchInput
aria-label="Search"
placeholder="Search..."
value={searchQuery}
startContent={
<SearchIcon className="text-bg-button-secondary shrink-0" width={16} />
}
onChange={(e) => {
const value = e.target.value;
setSearchQuery(value);
debouncedChangeHandler(value);
}}
endContent={
searchQuery && (
<button
onClick={clearIconSearch}
className="text-bg-button-secondary shrink-0 focus:outline-none"
>
<XCircle className="text-bg-button-secondary h-4 w-4" />
</button>
)
}
onClear={clearIconSearch}
/>
);
};

Some files were not shown because too many files have changed in this diff Show More