mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
[CHAIN] refactor(ui): remove dead tokens from tailwind config (#11533)
This commit is contained in:
+2
-2
@@ -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
@@ -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)
|
||||
|
||||
@@ -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've Been Invited
|
||||
</h1>
|
||||
<p className="text-default-500 mt-2">
|
||||
<p className="text-text-neutral-tertiary mt-2">
|
||||
You've been invited to join a tenant on Prowler. How would
|
||||
you like to continue?
|
||||
</p>
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+6
-6
@@ -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
-2
@@ -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>
|
||||
|
||||
+3
-3
@@ -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">
|
||||
|
||||
+1
-1
@@ -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">
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+3
-3
@@ -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">
|
||||
|
||||
+3
-3
@@ -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,
|
||||
|
||||
+3
-3
@@ -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,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}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,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 }>;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 ({
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> */}
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import { ContentLayout } from "@/components/ui";
|
||||
import { ContentLayout } from "@/components/shadcn/content-layout";
|
||||
|
||||
export default async function Workloads() {
|
||||
return (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
<CustomLink size="md" href={href} target="_self">
|
||||
{linkText}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
<CustomLink
|
||||
@@ -223,8 +226,9 @@ export const SignUpForm = ({
|
||||
Terms of Service
|
||||
</CustomLink>
|
||||
of Prowler
|
||||
</Checkbox>
|
||||
</FormControl>
|
||||
<span className="text-text-error-primary">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<FormMessage className="text-text-error" />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user