diff --git a/docs/security/software-security.mdx b/docs/security/software-security.mdx index 3fee8d1251..64c2645d3f 100644 --- a/docs/security/software-security.mdx +++ b/docs/security/software-security.mdx @@ -158,7 +158,7 @@ The MCP Server has a small direct-dependency surface and does not yet declare a The UI uses [pnpm](https://pnpm.io) with supply-chain controls configured in [`ui/pnpm-workspace.yaml`](https://github.com/prowler-cloud/prowler/blob/master/ui/pnpm-workspace.yaml). - **Minimum release age** (`minimumReleaseAge: 1440`): packages must publish at least 24 hours before install. This reduces exposure during the window when a compromised release has not yet been detected and yanked. -- **Lifecycle script allow-list** (`strictDepBuilds: true` + `allowBuilds`): only explicitly approved packages may run `install` or `postinstall` scripts (currently `sharp`, `esbuild`, `@sentry/cli`, `@heroui/shared-utils`, `unrs-resolver`, `msw`). Any unlisted package with lifecycle scripts fails the install. +- **Lifecycle script allow-list** (`strictDepBuilds: true` + `allowBuilds`): only explicitly approved packages may run `install` or `postinstall` scripts (currently `sharp`, `esbuild`, `@sentry/cli`, `unrs-resolver`, `msw`). Any unlisted package with lifecycle scripts fails the install. - **Trust policy** (`trustPolicy: no-downgrade`): the install fails when a package's trust evidence drops, for example after a new publisher takes over. - **Block exotic subdeps** (`blockExoticSubdeps: true`): transitive dependencies cannot ship as git URLs or tarballs. Every package in the tree resolves from the configured registry. - **Transitive overrides** in [`ui/package.json`](https://github.com/prowler-cloud/prowler/blob/master/ui/package.json) force specific versions for transitive packages (`lodash`, `serialize-javascript`, `qs`, `rollup`, `minimatch`, `ajv`, and others). diff --git a/skills/prowler-ui/SKILL.md b/skills/prowler-ui/SKILL.md index 5846dd85d0..18d37a9cd2 100644 --- a/skills/prowler-ui/SKILL.md +++ b/skills/prowler-ui/SKILL.md @@ -2,11 +2,11 @@ name: prowler-ui description: > Prowler UI-specific patterns. For generic patterns, see: typescript, react-19, nextjs-16, tailwind-4. - Trigger: When working inside ui/ on Prowler-specific conventions (shadcn vs HeroUI legacy, folder placement, actions/adapters, shared types/hooks/lib). + Trigger: When working inside ui/ on Prowler-specific conventions (shadcn, folder placement, actions/adapters, shared types/hooks/lib). license: Apache-2.0 metadata: author: prowler-cloud - version: "1.0" + version: "1.1" scope: [root, ui] auto_invoke: - "Creating/modifying Prowler UI components" @@ -32,13 +32,12 @@ allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task 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 -HeroUI 2.8.4 (LEGACY - do not add new components) ``` ## CRITICAL: Component Library Rule - **ALWAYS**: Use `shadcn/ui` + Tailwind (`components/shadcn/`) -- **NEVER**: Add new HeroUI components (`components/ui/` is legacy only) +- **NEVER**: Add components to `components/ui/` (temporary re-export shims for the prowler-cloud overlay only) ## Design System Discipline (REQUIRED) @@ -57,12 +56,11 @@ When reviewing UI PRs, flag: custom modals/primitives that duplicate shadcn, cal ### Component Placement ```text -New feature UI? → shadcn/ui + Tailwind -Existing HeroUI feature? → Keep HeroUI (don't mix) -Used 1 feature? → features/{feature}/components/ -Used 2+ features? → components/shared/ -Needs state/hooks? → "use client" -Server component? → No directive needed +New UI primitive? → components/shadcn/ (shadcn/ui + Tailwind) +Used by 1 domain? → components/{domain}/ +Used by 2+ domains? → components/shared/ +Needs state/hooks? → "use client" +Server component? → No directive needed ``` ### Code Location @@ -76,10 +74,16 @@ Utils (shared 2+) → lib/ Utils (local 1) → {feature}/utils/ Hooks (shared 2+) → hooks/ Hooks (local 1) → {feature}/hooks.ts -shadcn components → components/shadcn/ -HeroUI components → components/ui/ (LEGACY) +UI primitive → components/shadcn/ +Domain component → components/{domain}/ ``` +> **Deprecated:** `components/ui/` is a temporary re-export shim that maps +> legacy import paths to `components/shadcn/` for the prowler-cloud overlay. +> HeroUI is fully removed. Never add or import components here — use +> `@/components/shadcn` (primitives) or `@/components/{domain}` instead. +> Delete the shim once the cloud repo migrates to `@/components/shadcn`. + ### Styling Decision ```text @@ -110,8 +114,9 @@ ui/ │ ├── services/ │ └── integrations/ ├── components/ -│ ├── shadcn/ # shadcn/ui (USE THIS) -│ ├── ui/ # HeroUI (LEGACY) +│ ├── shadcn/ # shadcn/ui primitives (USE THIS) +│ ├── shared/ # Cross-domain composed components (2+ domains) +│ ├── ui/ # DEPRECATED shim → re-exports shadcn (do not use) │ ├── {domain}/ # Domain-specific (compliance, findings, providers, etc.) │ ├── filters/ # Filter components │ ├── graphs/ # Chart components @@ -324,16 +329,6 @@ Before requesting re-review from a reviewer: - [ ] If you disagreed: the reply explains why with clear reasoning — do not leave threads silently open - [ ] Re-request review only after all threads are in a clean state -## Migrations Reference - -| From | To | Key Changes | -|------|-----|-------------| -| React 18 | 19.1 | Async components, React Compiler (no useMemo/useCallback) | -| Next.js 14 | 15.5 | Improved App Router, better streaming | -| NextUI | HeroUI 2.8.4 | Package rename only, same API | -| Zod 3 | 4 | `z.email()` not `z.string().email()`, `error` not `message` | -| AI SDK 4 | 5 | `@ai-sdk/react`, `sendMessage` not `handleSubmit`, `parts` not `content` | - ## Resources - **Documentation**: See [references/](references/) for links to local developer guide diff --git a/ui/.vitest-attachments/10e901bc49347998108ddd603b413d594a3921f3.png b/ui/.vitest-attachments/10e901bc49347998108ddd603b413d594a3921f3.png new file mode 100644 index 0000000000..1bf6ac5bd6 Binary files /dev/null and b/ui/.vitest-attachments/10e901bc49347998108ddd603b413d594a3921f3.png differ diff --git a/ui/.vitest-attachments/e6f22611d8493abc2bfeaeb5808f35df7eabcb6d.png b/ui/.vitest-attachments/e6f22611d8493abc2bfeaeb5808f35df7eabcb6d.png new file mode 100644 index 0000000000..54e4f258ce Binary files /dev/null and b/ui/.vitest-attachments/e6f22611d8493abc2bfeaeb5808f35df7eabcb6d.png differ diff --git a/ui/AGENTS.md b/ui/AGENTS.md index 898d70dde4..5939d11394 100644 --- a/ui/AGENTS.md +++ b/ui/AGENTS.md @@ -97,8 +97,8 @@ When performing these actions, ALWAYS invoke the corresponding skill FIRST: ### Component Placement ```text -New/Existing UI? → shadcn/ui + Tailwind (NEVER HeroUI for new code) -Used 1 feature? → features/{feature}/components | Used 2+? → components/{domain}/ +New UI primitive? → components/shadcn/ (shadcn/ui + Tailwind) +Used by 1 domain? → components/{domain}/ | Used by 2+ domains? → components/shared/ Needs state/hooks? → "use client" | Server component? → No directive ``` @@ -194,7 +194,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. --- @@ -205,7 +205,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 diff --git a/ui/README.md b/ui/README.md index 998dc84a37..31b6ae3057 100644 --- a/ui/README.md +++ b/ui/README.md @@ -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) diff --git a/ui/app/(auth)/alerts/confirm/page.test.tsx b/ui/app/(auth)/alerts/confirm/page.test.tsx index 8b6203c109..6885db86b3 100644 --- a/ui/app/(auth)/alerts/confirm/page.test.tsx +++ b/ui/app/(auth)/alerts/confirm/page.test.tsx @@ -16,7 +16,8 @@ vi.mock("@/components/auth/oss/auth-layout", () => ({ ), })); -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), Button: ({ children }: { children: ReactNode }) =>
{children}
, })); diff --git a/ui/app/(auth)/alerts/unsubscribe/page.test.tsx b/ui/app/(auth)/alerts/unsubscribe/page.test.tsx index 4ffeb548b0..560a5ad010 100644 --- a/ui/app/(auth)/alerts/unsubscribe/page.test.tsx +++ b/ui/app/(auth)/alerts/unsubscribe/page.test.tsx @@ -16,7 +16,8 @@ vi.mock("@/components/auth/oss/auth-layout", () => ({ ), })); -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), Button: ({ children }: { children: ReactNode }) =>
{children}
, })); diff --git a/ui/app/(auth)/invitation/accept/accept-invitation-client.tsx b/ui/app/(auth)/invitation/accept/accept-invitation-client.tsx index 0ed6346de7..cec9c8fc55 100644 --- a/ui/app/(auth)/invitation/accept/accept-invitation-client.tsx +++ b/ui/app/(auth)/invitation/accept/accept-invitation-client.tsx @@ -74,11 +74,11 @@ export function AcceptInvitationClient({

Invalid Invitation Link

-

+

No invitation token was provided. Please check the link you received.

@@ -93,11 +93,11 @@ export function AcceptInvitationClient({

Accepting Invitation...

-

+

Please wait while we process your invitation.

@@ -108,13 +108,13 @@ export function AcceptInvitationClient({

Could Not Accept Invitation

-

{state.message}

+

{state.message}

{state.canRetry && }
@@ -124,6 +120,7 @@ const SSRDataTable = async ({ columns={ColumnsInvitation} data={expandedResponse?.data || []} metadata={invitationsData?.meta} + showSearch /> ); }; diff --git a/ui/app/(prowler)/layout.tsx b/ui/app/(prowler)/layout.tsx index 2554ab02b9..ed63f95026 100644 --- a/ui/app/(prowler)/layout.tsx +++ b/ui/app/(prowler)/layout.tsx @@ -6,16 +6,16 @@ import { ReactNode, Suspense } from "react"; import { getProviders } from "@/actions/providers"; import { getScansByState } from "@/actions/scans/scans"; +import MainLayout from "@/components/layout/main-layout/main-layout"; import { OnboardingCheckpointWatcher, OnboardingGate, OnboardingSequenceBanner, } from "@/components/onboarding"; import { RuntimePublicConfig } from "@/components/runtime-config/runtime-public-config"; -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 { NavigationProgress } from "@/components/shadcn/navigation-progress"; +import { Toaster } from "@/components/shadcn/toast"; +import { fontMono, fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; import { isCloud } from "@/lib/shared/env"; import { cn } from "@/lib/utils"; @@ -83,8 +83,9 @@ export default async function RootLayout({ diff --git a/ui/app/(prowler)/lighthouse/_components/config/lighthouse-v2-config-page.test.tsx b/ui/app/(prowler)/lighthouse/_components/config/lighthouse-v2-config-page.test.tsx index a1f8d1a5ab..84a13ee741 100644 --- a/ui/app/(prowler)/lighthouse/_components/config/lighthouse-v2-config-page.test.tsx +++ b/ui/app/(prowler)/lighthouse/_components/config/lighthouse-v2-config-page.test.tsx @@ -29,7 +29,8 @@ vi.mock("next/navigation", () => ({ // Action feedback is delivered through toasts (rendered by the layout Toaster), // so we assert the dispatched toast rather than in-page banner text. -vi.mock("@/components/ui", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), useToast: () => ({ toast: toastMock, dismiss: vi.fn() }), })); diff --git a/ui/app/(prowler)/lighthouse/_components/config/lighthouse-v2-config-page.tsx b/ui/app/(prowler)/lighthouse/_components/config/lighthouse-v2-config-page.tsx index c5e3711c7a..07dd1ff255 100644 --- a/ui/app/(prowler)/lighthouse/_components/config/lighthouse-v2-config-page.tsx +++ b/ui/app/(prowler)/lighthouse/_components/config/lighthouse-v2-config-page.tsx @@ -11,8 +11,8 @@ import { type LighthouseV2ProviderType, type LighthouseV2SupportedProvider, } from "@/app/(prowler)/lighthouse/_types"; +import { useToast } from "@/components/shadcn"; import { Card } from "@/components/shadcn/card/card"; -import { useToast } from "@/components/ui"; import { useMountEffect } from "@/hooks/use-mount-effect"; import { LighthouseV2BusinessContextForm } from "./business-context-form"; diff --git a/ui/app/(prowler)/lighthouse/page.tsx b/ui/app/(prowler)/lighthouse/page.tsx index 7b7d6703db..c313a86794 100644 --- a/ui/app/(prowler)/lighthouse/page.tsx +++ b/ui/app/(prowler)/lighthouse/page.tsx @@ -15,7 +15,7 @@ import { LighthouseV2NavigationModeSync } from "@/app/(prowler)/lighthouse/_comp import { loadLighthouseV2ConnectedModels } from "@/app/(prowler)/lighthouse/_lib/model-loading"; import { LighthouseIcon } from "@/components/icons/Icons"; import { Chat } from "@/components/lighthouse-v1"; -import { ContentLayout } from "@/components/ui"; +import { ContentLayout } from "@/components/shadcn/content-layout"; import { LIGHTHOUSE_ROUTE } from "@/lib/lighthouse-routes"; import { isCloud } from "@/lib/shared/env"; diff --git a/ui/app/(prowler)/lighthouse/settings/(connect-llm)/layout.tsx b/ui/app/(prowler)/lighthouse/settings/(connect-llm)/layout.tsx index ae94cd635f..8c8db3f7c7 100644 --- a/ui/app/(prowler)/lighthouse/settings/(connect-llm)/layout.tsx +++ b/ui/app/(prowler)/lighthouse/settings/(connect-llm)/layout.tsx @@ -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-v1/forms/delete-llm-provider-form"; import { WorkflowConnectLLM } from "@/components/lighthouse-v1/workflow"; import { Button } from "@/components/shadcn"; +import { NavigationHeader } from "@/components/shadcn"; import { Modal } from "@/components/shadcn/modal"; -import { NavigationHeader } from "@/components/ui"; import { LIGHTHOUSE_ROUTE } from "@/lib/lighthouse-routes"; import type { LighthouseProvider } from "@/types/lighthouse-v1"; @@ -81,7 +80,7 @@ export default function ConnectLLMLayout({ children }: ConnectLLMLayoutProps) { icon="icon-park-outline:close-small" href={LIGHTHOUSE_ROUTE.SETTINGS} /> - +
@@ -114,7 +113,7 @@ export default function ConnectLLMLayout({ children }: ConnectLLMLayoutProps) { Delete Provider
- +
)} {children} diff --git a/ui/app/(prowler)/lighthouse/settings/page.tsx b/ui/app/(prowler)/lighthouse/settings/page.tsx index 057161bc10..c2d267e404 100644 --- a/ui/app/(prowler)/lighthouse/settings/page.tsx +++ b/ui/app/(prowler)/lighthouse/settings/page.tsx @@ -7,7 +7,7 @@ import { LighthouseSettings, LLMProvidersTable, } from "@/components/lighthouse-v1"; -import { ContentLayout } from "@/components/ui"; +import { ContentLayout } from "@/components/shadcn/content-layout"; import { isCloud } from "@/lib/shared/env"; export const dynamic = "force-dynamic"; diff --git a/ui/app/(prowler)/manage-groups/layout.tsx b/ui/app/(prowler)/manage-groups/layout.tsx index 8e8487934e..224c4b4018 100644 --- a/ui/app/(prowler)/manage-groups/layout.tsx +++ b/ui/app/(prowler)/manage-groups/layout.tsx @@ -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; diff --git a/ui/app/(prowler)/mutelist/_components/advanced-mutelist-form.tsx b/ui/app/(prowler)/mutelist/_components/advanced-mutelist-form.tsx index d6992d6fb5..5d30308ee3 100644 --- a/ui/app/(prowler)/mutelist/_components/advanced-mutelist-form.tsx +++ b/ui/app/(prowler)/mutelist/_components/advanced-mutelist-form.tsx @@ -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 ( @@ -159,7 +172,7 @@ export function AdvancedMutelistForm() { size="md" >
-

+

Are you sure you want to delete this configuration? This action cannot be undone.

@@ -193,10 +206,10 @@ export function AdvancedMutelistForm() {
-

+

Advanced Mutelist Configuration

-
    +
    • This Mutelist configuration will take effect on the next @@ -227,7 +240,7 @@ export function AdvancedMutelistForm() {
      @@ -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 && ( + + {configErrorMessage} + + )} {yamlValidation.isValid && configText && hasUserStartedTyping && ( -
      +
      Valid YAML format
      )} diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-edit-form.test.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-edit-form.test.tsx index 70b90187b6..1f13aaa09c 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-edit-form.test.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-edit-form.test.tsx @@ -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>()), + 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 diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-edit-form.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-edit-form.tsx index 1c90e86b90..96268f72e0 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-edit-form.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-edit-form.tsx @@ -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, diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-enabled-toggle.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-enabled-toggle.tsx index ec843e8415..73af798640 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-enabled-toggle.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-enabled-toggle.tsx @@ -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 ( ); diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-targets-modal.test.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-targets-modal.test.tsx new file mode 100644 index 0000000000..eb8db616e2 --- /dev/null +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-targets-modal.test.tsx @@ -0,0 +1,84 @@ +import { render, screen, within } from "@testing-library/react"; +import { type ReactNode } from "react"; +import { describe, expect, it, vi } from "vitest"; + +import { type MuteRuleTableData } from "./mute-rule-target-previews"; + +vi.mock("@/components/shadcn/modal", () => ({ + Modal: ({ + children, + open, + title, + }: { + children: ReactNode; + open: boolean; + title?: string; + }) => + open ? ( +
      + {children} +
      + ) : null, +})); + +import { MuteRuleTargetsModal } from "./mute-rule-targets-modal"; + +const longMuteRule: MuteRuleTableData = { + type: "mute-rules", + id: "mute-rule-1", + attributes: { + inserted_at: "2026-04-22T09:00:00Z", + updated_at: "2026-04-22T09:05:00Z", + name: "Finding triage: Risk Accepted - 019f1d6f-b304-78a6-8a59-9f648f65d123", + reason: "Existing reason", + enabled: true, + finding_uids: ["uid-1"], + }, + targetLabels: [ + "Security Hub is enabled with standards or integrations configured • hub/unknown", + ], + targetSummaryLabel: + "Security Hub is enabled with standards or integrations configured • hub/unknown", + hiddenTargetCount: 0, +}; + +describe("MuteRuleTargetsModal", () => { + it("keeps long mute rule and finding cards constrained to the modal width", () => { + // Given / When + render( + , + ); + + // Then + const dialog = screen.getByRole("dialog", { name: "Muted Findings" }); + const content = dialog.firstElementChild; + if (!(content instanceof HTMLElement)) { + throw new Error("Expected modal content wrapper"); + } + + const muteRuleCard = within(dialog) + .getByText("Mute rule") + .closest("div")?.parentElement; + if (!(muteRuleCard instanceof HTMLElement)) { + throw new Error("Expected mute rule card"); + } + + const targetList = within(dialog).getByRole("list"); + const targetsCard = targetList.parentElement; + if (!(targetsCard instanceof HTMLElement)) { + throw new Error("Expected targets card"); + } + + expect(content).toHaveClass("min-w-0", "max-w-full"); + expect(muteRuleCard).toHaveClass( + "min-w-0", + "max-w-full", + "overflow-hidden", + ); + expect(targetsCard).toHaveClass("min-w-0", "max-w-full", "overflow-hidden"); + }); +}); diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-targets-modal.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-targets-modal.tsx index d2e2783c9c..2ec6bbaac6 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rule-targets-modal.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rule-targets-modal.tsx @@ -29,9 +29,9 @@ export function MuteRuleTargetsModal({ description="Review every finding currently muted by this rule." size="xl" > -
      -
      -
      +
      +
      +

      Mute rule

      @@ -48,7 +48,7 @@ export function MuteRuleTargetsModal({
      -
      +
        {muteRule.targetLabels.map((label, index) => { const [title, ...metaParts] = label.split(" • "); @@ -57,7 +57,7 @@ export function MuteRuleTargetsModal({ return (
      • {title} diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-columns.test.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-columns.test.tsx index aeb7747184..89788b7d73 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-columns.test.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-columns.test.tsx @@ -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 }) => {title}, })); diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-columns.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-columns.tsx index 68968bb414..51d136e3fd 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-columns.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-columns.tsx @@ -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"; diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table-client.test.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table-client.test.tsx index 8aa0e8b354..64ac2cee32 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table-client.test.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table-client.test.tsx @@ -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>()), + 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; diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table-client.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table-client.tsx index da3a2478a3..17af6dd3c2 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table-client.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table-client.tsx @@ -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({ {/* Edit Modal */} {selectedMuteRule && ( setIsEditModalOpen(false)} /> )} @@ -127,8 +126,8 @@ export function MuteRulesTableClient({ {/* Delete Confirmation Modal */} {selectedMuteRule && ( setIsDeleteModalOpen(false)} submitText={isDeleting ? "Deleting..." : "Delete"} isDisabled={isDeleting} submitColor="danger" diff --git a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table.tsx b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table.tsx index 2cccb1fe1c..07b6208e83 100644 --- a/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table.tsx +++ b/ui/app/(prowler)/mutelist/_components/simple/mute-rules-table.tsx @@ -43,8 +43,10 @@ export async function MuteRulesTable({ searchParams }: MuteRulesTableProps) {

        Mute rules are created when you mute findings from the Findings - page. Select findings and click "Mute" to create your - first rule. + page. +
        + Select findings and click "Mute" to create your first + rule.

      @@ -94,7 +96,7 @@ export function MuteRulesTableSkeleton() { return (
      { + const currentDir = path.dirname(fileURLToPath(import.meta.url)); + const pagePath = path.join(currentDir, "page.tsx"); + const source = readFileSync(pagePath, "utf8"); + + it("uses one large card with profile sections stacked vertically", () => { + expect(source).toContain('aria-label="User profile settings"'); + expect(source).toContain('className="w-full gap-4 p-4 md:p-5"'); + expect(source).not.toContain("xl:grid-cols"); + expect(source).not.toContain('className="flex w-full flex-col gap-6"'); + + const sectionOrder = [ + " + source.indexOf(section), + ); + + expect(sectionIndexes).not.toContain(-1); + expect(sectionIndexes).toEqual([...sectionIndexes].sort((a, b) => a - b)); + }); +}); diff --git a/ui/app/(prowler)/profile/page.tsx b/ui/app/(prowler)/profile/page.tsx index c992734283..48f4554db6 100644 --- a/ui/app/(prowler)/profile/page.tsx +++ b/ui/app/(prowler)/profile/page.tsx @@ -4,7 +4,8 @@ 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 { Card } from "@/components/shadcn"; +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"; @@ -96,25 +97,25 @@ const SSRDataUser = async ({ const samlConfig = hasManageIntegrations ? await getSamlConfig() : undefined; return ( -
      + -
      -
      - - {hasManageIntegrations && ( - - )} -
      -
      - - {hasManageAccount && } -
      -
      -
      + + {hasManageIntegrations && ( + + )} + + {hasManageAccount && } + ); }; diff --git a/ui/app/(prowler)/providers/page.tsx b/ui/app/(prowler)/providers/page.tsx index ec9c0a7798..4d15db700f 100644 --- a/ui/app/(prowler)/providers/page.tsx +++ b/ui/app/(prowler)/providers/page.tsx @@ -4,8 +4,8 @@ import { listScanConfigurations } from "@/actions/scan-configurations"; 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"; import { diff --git a/ui/app/(prowler)/providers/provider-groups-content.tsx b/ui/app/(prowler)/providers/provider-groups-content.tsx index f732f131e5..51d1b30c39 100644 --- a/ui/app/(prowler)/providers/provider-groups-content.tsx +++ b/ui/app/(prowler)/providers/provider-groups-content.tsx @@ -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 ({ diff --git a/ui/app/(prowler)/resources/page.tsx b/ui/app/(prowler)/resources/page.tsx index 9c9227160c..4eb2268c30 100644 --- a/ui/app/(prowler)/resources/page.tsx +++ b/ui/app/(prowler)/resources/page.tsx @@ -12,7 +12,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, @@ -172,7 +172,7 @@ const SSRDataTable = async ({ return ( <> {resourcesData?.errors && ( -
      +

      Error:

      {resourcesData.errors[0].detail}

      diff --git a/ui/app/(prowler)/roles/(add-role)/layout.tsx b/ui/app/(prowler)/roles/(add-role)/layout.tsx index 32a1030426..1895ed6892 100644 --- a/ui/app/(prowler)/roles/(add-role)/layout.tsx +++ b/ui/app/(prowler)/roles/(add-role)/layout.tsx @@ -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" /> - +
      diff --git a/ui/app/(prowler)/roles/page.tsx b/ui/app/(prowler)/roles/page.tsx index 259776c5a3..6634d0358f 100644 --- a/ui/app/(prowler)/roles/page.tsx +++ b/ui/app/(prowler)/roles/page.tsx @@ -2,13 +2,11 @@ import Link from "next/link"; import { Suspense } from "react"; import { getRoles } from "@/actions/roles"; -import { FilterControls } from "@/components/filters"; 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({ @@ -21,16 +19,14 @@ export default async function Roles({ return ( - -
      - +
      @@ -67,6 +63,7 @@ const SSRDataTable = async ({ columns={ColumnsRoles} data={rolesData?.data || []} metadata={rolesData?.meta} + showSearch /> ); }; diff --git a/ui/app/(prowler)/scans/config/_components/scan-configuration-editor.tsx b/ui/app/(prowler)/scans/config/_components/scan-configuration-editor.tsx index 06e58359c1..1583ccc527 100644 --- a/ui/app/(prowler)/scans/config/_components/scan-configuration-editor.tsx +++ b/ui/app/(prowler)/scans/config/_components/scan-configuration-editor.tsx @@ -18,9 +18,9 @@ import { Input, 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 { DOCS_URLS } from "@/lib/external-urls"; import { convertToYaml, @@ -190,10 +190,10 @@ function ScanConfigurationForm({ Configuration (YAML) -
        +
        • Follows the structure of{" "} - + prowler/config/config.yaml ; include only the keys you want to override. Learn more{" "} @@ -225,7 +225,7 @@ function ScanConfigurationForm({ {!yamlSyntax.isValid ? ( {`Invalid YAML format: ${yamlSyntax.error}`} ) : configText.trim() && !configError ? ( -

          +

          Valid YAML format

          ) : null} @@ -235,7 +235,7 @@ function ScanConfigurationForm({ Attach to providers (optional) -

          +

          Pick the providers that should use this configuration on their next scan. You can save it without any and attach providers later — it just won't apply to a scan until one is attached. @@ -249,7 +249,7 @@ function ScanConfigurationForm({ )}

          {selectableProviders.length === 0 ? ( -

          +

          {richProviders.length === 0 ? "No providers available in this tenant." : "All providers are already attached to other Scan Configurations."} diff --git a/ui/app/(prowler)/scans/config/_components/scan-configurations-columns.tsx b/ui/app/(prowler)/scans/config/_components/scan-configurations-columns.tsx index 60d41bae37..421962c6fe 100644 --- a/ui/app/(prowler)/scans/config/_components/scan-configurations-columns.tsx +++ b/ui/app/(prowler)/scans/config/_components/scan-configurations-columns.tsx @@ -8,8 +8,8 @@ import { ActionDropdownDangerZone, ActionDropdownItem, } from "@/components/shadcn/dropdown"; -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 { ScanConfigurationData } from "@/types/scan-configurations"; export const createScanConfigurationsColumns = ( diff --git a/ui/app/(prowler)/scans/config/_components/scan-configurations-manager.tsx b/ui/app/(prowler)/scans/config/_components/scan-configurations-manager.tsx index e78f7583ce..2599b7291b 100644 --- a/ui/app/(prowler)/scans/config/_components/scan-configurations-manager.tsx +++ b/ui/app/(prowler)/scans/config/_components/scan-configurations-manager.tsx @@ -11,10 +11,10 @@ import { AccountsSelector } from "@/app/(prowler)/_overview/_components/accounts import { BatchFiltersLayout } from "@/components/filters/batch-filters-layout"; import { ClearFiltersButton } from "@/components/filters/clear-filters-button"; import { Button, Card } 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 { DataTable } from "@/components/ui/table"; +import { DataTable } from "@/components/shadcn/table"; import { DOCS_URLS } from "@/lib/external-urls"; import { ProviderProps } from "@/types/providers"; import { ScanConfigurationData } from "@/types/scan-configurations"; @@ -199,12 +199,12 @@ export function ScanConfigurationsManager({ {noMatchForProvider ? (

          -

          +

          {providerFilter.length === 1 ? "No Scan Configuration is attached to this provider." : "No Scan Configuration is attached to any of the selected providers."}

          -

          +

          The next scan{providerFilter.length === 1 ? "" : "s"} will use the built-in defaults shipped with Prowler. Attach a Scan Configuration from the editor to override them. @@ -241,7 +241,7 @@ export function ScanConfigurationsManager({ size="md" >

          -

          +

          Are you sure you want to delete{" "} {pendingDelete?.attributes.name}? Attached providers will fall back to the built-in scan defaults on their next diff --git a/ui/app/(prowler)/scans/config/page.tsx b/ui/app/(prowler)/scans/config/page.tsx index de213a5dab..5319bf6ab1 100644 --- a/ui/app/(prowler)/scans/config/page.tsx +++ b/ui/app/(prowler)/scans/config/page.tsx @@ -2,7 +2,7 @@ import { redirect } from "next/navigation"; import { getProviders } from "@/actions/providers"; import { listScanConfigurations } from "@/actions/scan-configurations"; -import { ContentLayout } from "@/components/ui"; +import { ContentLayout } from "@/components/shadcn/content-layout"; import { isCloud } from "@/lib/shared/env"; import { ScanConfigurationsManager } from "./_components/scan-configurations-manager"; diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index 9274221a4b..f307920e52 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -24,7 +24,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 { buildProviderScheduleSummary, buildSchedulesByProviderId, diff --git a/ui/app/(prowler)/services/page.tsx b/ui/app/(prowler)/services/page.tsx index 78e8ddbccc..fc80e6cb4c 100644 --- a/ui/app/(prowler)/services/page.tsx +++ b/ui/app/(prowler)/services/page.tsx @@ -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" > - +

          - +
          {/* }> */} diff --git a/ui/app/(prowler)/users/page.tsx b/ui/app/(prowler)/users/page.tsx index fa1e838872..fc7bb8f6f1 100644 --- a/ui/app/(prowler)/users/page.tsx +++ b/ui/app/(prowler)/users/page.tsx @@ -4,11 +4,9 @@ import { Suspense } from "react"; import { getRoles } from "@/actions/roles/roles"; import { getCurrentUserTenantRole, getUsers } from "@/actions/users/users"; 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"; @@ -23,15 +21,10 @@ export default async function Users({ return ( - -
          @@ -121,6 +114,7 @@ const SSRDataTable = async ({ columns={ColumnsUser} data={expandedUsers || []} metadata={usersData?.meta} + showSearch /> ); }; diff --git a/ui/app/(prowler)/workloads/page.tsx b/ui/app/(prowler)/workloads/page.tsx index 5ffd6a9802..252e35ef6f 100644 --- a/ui/app/(prowler)/workloads/page.tsx +++ b/ui/app/(prowler)/workloads/page.tsx @@ -1,4 +1,4 @@ -import { ContentLayout } from "@/components/ui"; +import { ContentLayout } from "@/components/shadcn/content-layout"; export default async function Workloads() { return ( diff --git a/ui/app/providers.tsx b/ui/app/providers.tsx index 41157df06c..6dc47cf42d 100644 --- a/ui/app/providers.tsx +++ b/ui/app/providers.tsx @@ -1,25 +1,19 @@ "use 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 ( - - {children} - + {children} ); } diff --git a/ui/changelog.d/heroui-shadcn-migration.changed.md b/ui/changelog.d/heroui-shadcn-migration.changed.md new file mode 100644 index 0000000000..9f0b4a8b1c --- /dev/null +++ b/ui/changelog.d/heroui-shadcn-migration.changed.md @@ -0,0 +1 @@ +UI components migrated from HeroUI to shared shadcn primitives diff --git a/ui/components/ThemeSwitch.tsx b/ui/components/ThemeSwitch.tsx index f9c039db93..4979dec541 100644 --- a/ui/components/ThemeSwitch.tsx +++ b/ui/components/ThemeSwitch.tsx @@ -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 = ({ - 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 ( - 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, + )} > - - - -
          - {!isSelected || isSSR ? ( - - ) : ( - - )} -
          -
          + {isLightMode && isHydrated ? ( + + ) : ( + + )} +
          - {isSelected || isSSR ? "Switch to Dark Mode" : "Switch to Light Mode"} + {isLightMode ? "Switch to Dark Mode" : "Switch to Light Mode"}
          ); -}; +} diff --git a/ui/components/auth/oss/auth-divider.tsx b/ui/components/auth/oss/auth-divider.tsx index 2951a1a01c..2d9fe56b30 100644 --- a/ui/components/auth/oss/auth-divider.tsx +++ b/ui/components/auth/oss/auth-divider.tsx @@ -1,11 +1,11 @@ -import { Divider } from "@heroui/divider"; +import { Separator } from "@/components/shadcn"; export const AuthDivider = () => { return (
          - -

          OR

          - + +

          OR

          +
          ); }; diff --git a/ui/components/auth/oss/auth-footer-link.tsx b/ui/components/auth/oss/auth-footer-link.tsx index 221a48a400..91c6a89c95 100644 --- a/ui/components/auth/oss/auth-footer-link.tsx +++ b/ui/components/auth/oss/auth-footer-link.tsx @@ -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 ( -

          +

          {text}  {linkText} diff --git a/ui/components/auth/oss/auth-layout.tsx b/ui/components/auth/oss/auth-layout.tsx index 84d47c218c..b33e4d4c32 100644 --- a/ui/components/auth/oss/auth-layout.tsx +++ b/ui/components/auth/oss/auth-layout.tsx @@ -27,7 +27,7 @@ export const AuthLayout = ({ title, children }: AuthLayoutProps) => {

          {/* Auth Form Container */} -
          +
          {/* Header with Title and Theme Toggle */}

          {title}

          diff --git a/ui/components/auth/oss/sign-in-form.tsx b/ui/components/auth/oss/sign-in-form.tsx index 0d4f1960e9..64f3352279 100644 --- a/ui/components/auth/oss/sign-in-form.tsx +++ b/ui/components/auth/oss/sign-in-form.tsx @@ -13,10 +13,11 @@ 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 { getSafeCallbackPath } from "@/lib/auth-callback-url"; +import { stripPasswordManagerHighlight } from "@/lib/password-manager"; import { SignInFormData, signInSchema } from "@/types"; export const SignInForm = ({ @@ -147,6 +148,7 @@ export const SignInForm = ({
          {!isSamlMode && ( diff --git a/ui/components/auth/oss/sign-up-form.tsx b/ui/components/auth/oss/sign-up-form.tsx index df5cb0ba12..ac0282aec0 100644 --- a/ui/components/auth/oss/sign-up-form.tsx +++ b/ui/components/auth/oss/sign-up-form.tsx @@ -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,17 @@ 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 { stripPasswordManagerHighlight } from "@/lib/password-manager"; import { ApiError, SignUpFormData, signUpSchema } from "@/types"; const AUTH_ERROR_PATHS = { @@ -164,6 +164,7 @@ export const SignUpForm = ({ ( <> - - field.onChange(e.target.checked)} - color="default" +
          + + + + +
          )} diff --git a/ui/components/auth/oss/social-buttons.tsx b/ui/components/auth/oss/social-buttons.tsx index 65a891a743..b08d830fb5 100644 --- a/ui/components/auth/oss/social-buttons.tsx +++ b/ui/components/auth/oss/social-buttons.tsx @@ -7,7 +7,7 @@ import { TooltipContent, TooltipTrigger, } from "@/components/shadcn"; -import { CustomLink } from "@/components/ui/custom/custom-link"; +import { CustomLink } from "@/components/shadcn/custom/custom-link"; import { appendCallbackState } from "@/lib/auth-callback-url"; type SocialProvider = { @@ -73,7 +73,7 @@ const SocialButton = ({ {provider.isOAuthEnabled ? ( disabledTooltipContent ) : ( -
          +
          {provider.disabledDocs.message}{" "} Read the docs diff --git a/ui/components/compliance/compliance-accordion/client-accordion-content.tsx b/ui/components/compliance/compliance-accordion/client-accordion-content.tsx index 1ad22792f4..d714a6c764 100644 --- a/ui/components/compliance/compliance-accordion/client-accordion-content.tsx +++ b/ui/components/compliance/compliance-accordion/client-accordion-content.tsx @@ -12,8 +12,8 @@ import { SkeletonTableFindings, } from "@/components/findings/table"; import { Alert, AlertDescription, Button } from "@/components/shadcn"; -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 { FINDINGS_DEFAULT_SORT, MUTED_FILTER } from "@/lib"; import { INVALID_CONFIG_NOTE } from "@/lib/compliance/commons"; import { getComplianceMapper } from "@/lib/compliance/compliance-mapper"; @@ -122,7 +122,7 @@ export const ClientAccordionContent = ({ key: "checks", title: (
          - {checks.length} + {checks.length} {checks.length > 1 ? Checks : Check}
          ), @@ -197,7 +197,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" />
          )} diff --git a/ui/components/compliance/compliance-accordion/client-accordion-wrapper.tsx b/ui/components/compliance/compliance-accordion/client-accordion-wrapper.tsx index 4682f039e5..08e8658ab9 100644 --- a/ui/components/compliance/compliance-accordion/client-accordion-wrapper.tsx +++ b/ui/components/compliance/compliance-accordion/client-accordion-wrapper.tsx @@ -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(() => { diff --git a/ui/components/compliance/compliance-accordion/compliance-accordion-requeriment-title.tsx b/ui/components/compliance/compliance-accordion/compliance-accordion-requeriment-title.tsx index 99e5c1b8a4..aeae6bc03c 100644 --- a/ui/components/compliance/compliance-accordion/compliance-accordion-requeriment-title.tsx +++ b/ui/components/compliance/compliance-accordion/compliance-accordion-requeriment-title.tsx @@ -1,5 +1,5 @@ import { InfoTooltip } from "@/components/shadcn/info-field/info-field"; -import { FindingStatus, StatusFindingBadge } from "@/components/ui/table"; +import { FindingStatus, StatusFindingBadge } from "@/components/shadcn/table"; import { INVALID_CONFIG_NOTE } from "@/lib/compliance/commons"; interface ComplianceAccordionRequirementTitleProps { @@ -19,7 +19,7 @@ export const ComplianceAccordionRequirementTitle = ({
          {type && ( - + {type} )} diff --git a/ui/components/compliance/compliance-accordion/compliance-accordion-title.tsx b/ui/components/compliance/compliance-accordion/compliance-accordion-title.tsx index 97e700e5a5..0469f93daf 100644 --- a/ui/components/compliance/compliance-accordion/compliance-accordion-title.tsx +++ b/ui/components/compliance/compliance-accordion/compliance-accordion-title.tsx @@ -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 ? (
          {pass > 0 && ( - + + 0 ? "2px" : "0", + }} + /> + +
          Pass
          -
          +
          {pass} ({passPercentage.toFixed(1)}%)
          - } - size="sm" - placement="top" - delay={0} - closeDelay={0} - > - 0 ? "2px" : "0", - }} - /> + )} {fail > 0 && ( - + + 0 ? "2px" : "0", + }} + /> + +
          Fail
          -
          +
          {fail} ({failPercentage.toFixed(1)}%)
          - } - size="sm" - placement="top" - delay={0} - closeDelay={0} - > - 0 ? "2px" : "0", - }} - /> + )} {manual > 0 && ( - + + + +
          Manual
          -
          +
          {manual} ({manualPercentage.toFixed(1)}%)
          - } - size="sm" - placement="top" - delay={0} - closeDelay={0} - > - + )}
          @@ -117,19 +108,18 @@ export const ComplianceAccordionTitle = ({ )}
          - + + + {total > 0 ? total : "—"} + + +
          Total requirements
          -
          {total}
          +
          {total}
          - } - size="sm" - placement="top" - > - - {total > 0 ? total : "—"} - +
          diff --git a/ui/components/compliance/compliance-card.test.tsx b/ui/components/compliance/compliance-card.test.tsx index 996c8cb979..93b8b17db3 100644 --- a/ui/components/compliance/compliance-card.test.tsx +++ b/ui/components/compliance/compliance-card.test.tsx @@ -18,11 +18,6 @@ describe("ComplianceCard", () => { expect(source).not.toContain("sm:flex-row"); }); - it("uses the shadcn progress component instead of Hero UI", () => { - expect(source).toContain('from "@/components/shadcn/progress"'); - expect(source).not.toContain("@heroui/progress"); - }); - it("places compact actions in the icon column on larger screens", () => { expect(source).toContain('orientation="column"'); expect(source).toContain('buttonWidth="icon"'); diff --git a/ui/components/compliance/compliance-card.tsx b/ui/components/compliance/compliance-card.tsx index 3972bb8db4..6168784f2f 100644 --- a/ui/components/compliance/compliance-card.tsx +++ b/ui/components/compliance/compliance-card.tsx @@ -131,7 +131,7 @@ export const ComplianceCard: React.FC = ({
          -

          +

          {formatTitle(title)} {version ? ` - ${version}` : ""}

          diff --git a/ui/components/compliance/compliance-charts/heatmap-chart.tsx b/ui/components/compliance/compliance-charts/heatmap-chart.tsx index ff641a73c2..2d7ce547b3 100644 --- a/ui/components/compliance/compliance-charts/heatmap-chart.tsx +++ b/ui/components/compliance/compliance-charts/heatmap-chart.tsx @@ -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 { diff --git a/ui/components/compliance/compliance-charts/threatscore-breakdown-card.tsx b/ui/components/compliance/compliance-charts/threatscore-breakdown-card.tsx index e919588169..7b5aab2110 100644 --- a/ui/components/compliance/compliance-charts/threatscore-breakdown-card.tsx +++ b/ui/components/compliance/compliance-charts/threatscore-breakdown-card.tsx @@ -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 */}
          - + Score by Pillar
          @@ -80,7 +80,9 @@ export function ThreatScoreBreakdownCard({ {pillars.map(({ name, score, hasData }) => (
          - {name} + + {name} +
          @@ -113,22 +116,22 @@ export function ThreatScoreBreakdownCardSkeleton() { return ( -
          +
          {/* Mobile: vertical, Tablet: horizontal, Desktop: vertical */}
          -
          +
          {Array.from({ length: SKELETON_PILLAR_COUNT }, (_, i) => (
          -
          -
          +
          +
          -
          +
          ))}
          diff --git a/ui/components/compliance/compliance-custom-details/asd-essential-eight-details.test.tsx b/ui/components/compliance/compliance-custom-details/asd-essential-eight-details.test.tsx index 816b62a262..07c36b42de 100644 --- a/ui/components/compliance/compliance-custom-details/asd-essential-eight-details.test.tsx +++ b/ui/components/compliance/compliance-custom-details/asd-essential-eight-details.test.tsx @@ -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 `` 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 }) => ( {children} ), diff --git a/ui/components/compliance/compliance-custom-details/asd-essential-eight-details.tsx b/ui/components/compliance/compliance-custom-details/asd-essential-eight-details.tsx index 2cb022a174..975e1557cc 100644 --- a/ui/components/compliance/compliance-custom-details/asd-essential-eight-details.tsx +++ b/ui/components/compliance/compliance-custom-details/asd-essential-eight-details.tsx @@ -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, diff --git a/ui/components/compliance/compliance-custom-details/aws-well-architected-details.tsx b/ui/components/compliance/compliance-custom-details/aws-well-architected-details.tsx index 00aa51e4c5..b9e4baf369 100644 --- a/ui/components/compliance/compliance-custom-details/aws-well-architected-details.tsx +++ b/ui/components/compliance/compliance-custom-details/aws-well-architected-details.tsx @@ -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 { diff --git a/ui/components/compliance/compliance-custom-details/cis-details.tsx b/ui/components/compliance/compliance-custom-details/cis-details.tsx index 25823038d5..31f32576b0 100644 --- a/ui/components/compliance/compliance-custom-details/cis-details.tsx +++ b/ui/components/compliance/compliance-custom-details/cis-details.tsx @@ -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 { diff --git a/ui/components/compliance/compliance-custom-details/mitre-details.tsx b/ui/components/compliance/compliance-custom-details/mitre-details.tsx index 53fc583f2c..be53382036 100644 --- a/ui/components/compliance/compliance-custom-details/mitre-details.tsx +++ b/ui/components/compliance/compliance-custom-details/mitre-details.tsx @@ -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 { diff --git a/ui/components/compliance/compliance-custom-details/okta-idaas-stig-details.tsx b/ui/components/compliance/compliance-custom-details/okta-idaas-stig-details.tsx index 0869b7f390..695b39bdcd 100644 --- a/ui/components/compliance/compliance-custom-details/okta-idaas-stig-details.tsx +++ b/ui/components/compliance/compliance-custom-details/okta-idaas-stig-details.tsx @@ -1,4 +1,4 @@ -import { Severity, SeverityBadge } from "@/components/ui/table"; +import { Severity, SeverityBadge } from "@/components/shadcn/table"; import { Requirement } from "@/types/compliance"; import { diff --git a/ui/components/compliance/compliance-download-container.test.tsx b/ui/components/compliance/compliance-download-container.test.tsx index 31c68fb1fc..34f877a789 100644 --- a/ui/components/compliance/compliance-download-container.test.tsx +++ b/ui/components/compliance/compliance-download-container.test.tsx @@ -22,7 +22,8 @@ vi.mock("@/lib/helper", () => ({ downloadCompliancePdf: downloadCompliancePdfMock, })); -vi.mock("@/components/ui", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), toast: {}, })); @@ -39,7 +40,6 @@ describe("ComplianceDownloadContainer", () => { it("uses the shared action dropdown for the card actions mode", () => { expect(source).toContain("ActionDropdown"); - expect(source).not.toContain("@heroui/button"); }); it("should expose an accessible actions menu trigger", () => { diff --git a/ui/components/compliance/compliance-download-container.tsx b/ui/components/compliance/compliance-download-container.tsx index 6e29acc213..a5a00bfad5 100644 --- a/ui/components/compliance/compliance-download-container.tsx +++ b/ui/components/compliance/compliance-download-container.tsx @@ -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, diff --git a/ui/components/compliance/compliance-header/compliance-header.tsx b/ui/components/compliance/compliance-header/compliance-header.tsx index c53a5309a5..b5bf70c371 100644 --- a/ui/components/compliance/compliance-header/compliance-header.tsx +++ b/ui/components/compliance/compliance-header/compliance-header.tsx @@ -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"; diff --git a/ui/components/compliance/compliance-header/compliance-scan-info.tsx b/ui/components/compliance/compliance-header/compliance-scan-info.tsx index da1caa5ee9..f596b486e4 100644 --- a/ui/components/compliance/compliance-header/compliance-scan-info.tsx +++ b/ui/components/compliance/compliance-header/compliance-scan-info.tsx @@ -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} />
          - +
          - -

          + + +

          + {scan.attributes.name || "- -"} +

          + + {scan.attributes.name || "- -"} -

          +
          diff --git a/ui/components/compliance/compliance-overview-grid.tsx b/ui/components/compliance/compliance-overview-grid.tsx index b042ccc658..5900b23a86 100644 --- a/ui/components/compliance/compliance-overview-grid.tsx +++ b/ui/components/compliance/compliance-overview-grid.tsx @@ -5,7 +5,7 @@ import { Suspense, useState } from "react"; import { ComplianceCard } from "@/components/compliance/compliance-card"; import { OnboardingTrigger, PageReady } from "@/components/onboarding"; -import { DataTableSearch } from "@/components/ui/table/data-table-search"; +import { DataTableSearch } from "@/components/shadcn/table/data-table-search"; import { buildComplianceDetailPath } from "@/lib/compliance/compliance-detail-url"; import { getFlowById } from "@/lib/onboarding"; import { createViewComplianceTourStepHandlers } from "@/lib/tours/view-compliance.tour"; diff --git a/ui/components/compliance/skeletons/bar-chart-skeleton.tsx b/ui/components/compliance/skeletons/bar-chart-skeleton.tsx index e83813edfe..bcbe3c9198 100644 --- a/ui/components/compliance/skeletons/bar-chart-skeleton.tsx +++ b/ui/components/compliance/skeletons/bar-chart-skeleton.tsx @@ -1,14 +1,12 @@ "use client"; -import { Skeleton } from "@heroui/skeleton"; +import { Skeleton } from "@/components/shadcn"; export const BarChartSkeleton = () => { return (
          {/* Title skeleton */} - -
          - + {/* Chart area skeleton */}
          @@ -28,9 +26,7 @@ export const BarChartSkeleton = () => { ? "w-24" : "w-16" }`} - > -
          - + />
          ))} @@ -38,12 +34,8 @@ export const BarChartSkeleton = () => {
          {Array.from({ length: 3 }).map((_, index) => (
          - -
          - - -
          - + +
          ))}
          diff --git a/ui/components/compliance/skeletons/compliance-accordion-skeleton.tsx b/ui/components/compliance/skeletons/compliance-accordion-skeleton.tsx index 322336bd94..5ea3e56633 100644 --- a/ui/components/compliance/skeletons/compliance-accordion-skeleton.tsx +++ b/ui/components/compliance/skeletons/compliance-accordion-skeleton.tsx @@ -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) => ( - -
          -
          + ))}
          ); diff --git a/ui/components/compliance/skeletons/compliance-grid-skeleton.test.tsx b/ui/components/compliance/skeletons/compliance-grid-skeleton.test.tsx deleted file mode 100644 index c7621aab04..0000000000 --- a/ui/components/compliance/skeletons/compliance-grid-skeleton.test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { readFileSync } from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -import { describe, expect, it } from "vitest"; - -describe("ComplianceSkeletonGrid", () => { - const currentDir = path.dirname(fileURLToPath(import.meta.url)); - const filePath = path.join(currentDir, "compliance-grid-skeleton.tsx"); - const source = readFileSync(filePath, "utf8"); - - it("uses shadcn skeletons instead of Hero UI", () => { - expect(source).toContain('from "@/components/shadcn/skeleton/skeleton"'); - expect(source).not.toContain("@heroui/card"); - expect(source).not.toContain("@heroui/skeleton"); - }); -}); diff --git a/ui/components/compliance/skeletons/heatmap-chart-skeleton.tsx b/ui/components/compliance/skeletons/heatmap-chart-skeleton.tsx index dbc8a5e389..beaea5dd93 100644 --- a/ui/components/compliance/skeletons/heatmap-chart-skeleton.tsx +++ b/ui/components/compliance/skeletons/heatmap-chart-skeleton.tsx @@ -1,25 +1,18 @@ "use client"; -import { Skeleton } from "@heroui/skeleton"; +import { Skeleton } from "@/components/shadcn"; export const HeatmapChartSkeleton = () => { return (
          {/* Title skeleton */} - -
          - + {/* Heatmap area skeleton - 3x3 grid like the real component */}
          {Array.from({ length: 9 }).map((_, index) => ( - -
          - + ))}
          diff --git a/ui/components/compliance/skeletons/pie-chart-skeleton.tsx b/ui/components/compliance/skeletons/pie-chart-skeleton.tsx index c819f486ae..6c0035aec6 100644 --- a/ui/components/compliance/skeletons/pie-chart-skeleton.tsx +++ b/ui/components/compliance/skeletons/pie-chart-skeleton.tsx @@ -1,61 +1,41 @@ "use client"; -import { Skeleton } from "@heroui/skeleton"; +import { Skeleton } from "@/components/shadcn"; export const PieChartSkeleton = () => { return (
          {/* Title skeleton */} - -
          - + {/* Pie chart skeleton */}
          {/* Outer circle */} - -
          - + {/* Inner circle (donut hole) */} -
          +
          {/* Center text skeleton */}
          - -
          - - -
          - + +
          {/* Bottom stats skeleton */}
          - -
          - - -
          - + +
          - -
          - - -
          - + +
          - -
          - - -
          - + +
          diff --git a/ui/components/compliance/threatscore-badge.test.tsx b/ui/components/compliance/threatscore-badge.test.tsx index 2151f9bbf0..94ddf6d0df 100644 --- a/ui/components/compliance/threatscore-badge.test.tsx +++ b/ui/components/compliance/threatscore-badge.test.tsx @@ -9,13 +9,6 @@ describe("ThreatScoreBadge", () => { const filePath = path.join(currentDir, "threatscore-badge.tsx"); const source = readFileSync(filePath, "utf8"); - it("uses shadcn card and progress components instead of Hero UI", () => { - expect(source).toContain('from "@/components/shadcn/card/card"'); - expect(source).toContain('from "@/components/shadcn/progress"'); - expect(source).not.toContain("@heroui/card"); - expect(source).not.toContain("@heroui/progress"); - }); - it("uses ActionDropdown for downloads instead of ComplianceDownloadContainer", () => { expect(source).toContain("ActionDropdown"); expect(source).toContain("ActionDropdownItem"); diff --git a/ui/components/compliance/threatscore-badge.tsx b/ui/components/compliance/threatscore-badge.tsx index 01bf180c9f..f3e526fc3b 100644 --- a/ui/components/compliance/threatscore-badge.tsx +++ b/ui/components/compliance/threatscore-badge.tsx @@ -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, diff --git a/ui/components/filters/apply-filters-button.test.tsx b/ui/components/filters/apply-filters-button.test.tsx index fb6be26b57..81d36690a3 100644 --- a/ui/components/filters/apply-filters-button.test.tsx +++ b/ui/components/filters/apply-filters-button.test.tsx @@ -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>()), Button: ({ children, disabled, diff --git a/ui/components/filters/custom-search-input.tsx b/ui/components/filters/custom-search-input.tsx index f7b1366f4b..ccf96b0a4b 100644 --- a/ui/components/filters/custom-search-input.tsx +++ b/ui/components/filters/custom-search-input.tsx @@ -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(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 ( - *]:!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", - }} + - } onChange={(e) => { const value = e.target.value; setSearchQuery(value); debouncedChangeHandler(value); }} - endContent={ - searchQuery && ( - - ) - } + onClear={clearIconSearch} /> ); }; diff --git a/ui/components/filters/filter-controls.tsx b/ui/components/filters/filter-controls.tsx index b0403a9a66..94072d8c38 100644 --- a/ui/components/filters/filter-controls.tsx +++ b/ui/components/filters/filter-controls.tsx @@ -1,8 +1,8 @@ "use client"; +import { DataTableFilterCustom } from "@/components/shadcn/table"; import { FilterOption } from "@/types"; -import { DataTableFilterCustom } from "../ui/table"; import { CustomSearchInput } from "./custom-search-input"; export interface FilterControlsProps { diff --git a/ui/components/filters/filter-summary-strip.test.tsx b/ui/components/filters/filter-summary-strip.test.tsx index 57bfca2e14..75d02b7ae3 100644 --- a/ui/components/filters/filter-summary-strip.test.tsx +++ b/ui/components/filters/filter-summary-strip.test.tsx @@ -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>()), Badge: ({ children, className, diff --git a/ui/components/findings/findings-filters.tsx b/ui/components/findings/findings-filters.tsx index cf25eb9529..608ad97cd6 100644 --- a/ui/components/findings/findings-filters.tsx +++ b/ui/components/findings/findings-filters.tsx @@ -16,8 +16,8 @@ import { import { ProviderAccountSelectors } from "@/components/filters/provider-account-selectors"; import { ProviderGroupSelector } from "@/components/filters/provider-group-selector"; import { Button } from "@/components/shadcn"; -import { ExpandableSection } from "@/components/ui/expandable-section"; -import { DataTableFilterCustom } from "@/components/ui/table/data-table-filter-custom"; +import { ExpandableSection } from "@/components/shadcn/expandable-section"; +import { DataTableFilterCustom } from "@/components/shadcn/table/data-table-filter-custom"; import { useFilterBatch } from "@/hooks/use-filter-batch"; import { getCategoryLabel, getGroupLabel } from "@/lib/categories"; import { FILTER_FIELD, ScanEntity } from "@/types"; diff --git a/ui/components/findings/mute-findings-modal.test.tsx b/ui/components/findings/mute-findings-modal.test.tsx index 7f323da7cc..d80305c473 100644 --- a/ui/components/findings/mute-findings-modal.test.tsx +++ b/ui/components/findings/mute-findings-modal.test.tsx @@ -12,7 +12,9 @@ vi.mock("@/actions/mute-rules", () => ({ createMuteRule: createMuteRuleMock, })); -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), + useToast: () => ({ toast: toastMock }), Button: ({ children, ...props @@ -56,13 +58,7 @@ vi.mock("@/components/shadcn/spinner/spinner", () => ({ ), })); -vi.mock("@/components/ui", () => ({ - useToast: () => ({ - toast: toastMock, - }), -})); - -vi.mock("@/components/ui/form", () => ({ +vi.mock("@/components/shadcn/form", () => ({ FormButtons: ({ onCancel, submitText = "Save", diff --git a/ui/components/findings/mute-findings-modal.tsx b/ui/components/findings/mute-findings-modal.tsx index 999f0dead9..a46a0d8ab2 100644 --- a/ui/components/findings/mute-findings-modal.tsx +++ b/ui/components/findings/mute-findings-modal.tsx @@ -5,10 +5,10 @@ import { Dispatch, SetStateAction, useState } from "react"; import { createMuteRule } from "@/actions/mute-rules"; import { MuteRuleActionState } from "@/actions/mute-rules/types"; import { Button, Input, Textarea } from "@/components/shadcn"; +import { FormButtons } from "@/components/shadcn/form"; +import { Label } from "@/components/shadcn/form/Label"; import { Modal } from "@/components/shadcn/modal"; import { Skeleton } from "@/components/shadcn/skeleton/skeleton"; -import { FormButtons } from "@/components/ui/form"; -import { Label } from "@/components/ui/form/Label"; import { useMuteRuleAction } from "@/hooks/use-mute-rule-action"; import { enforceMuteRuleNameLimit, diff --git a/ui/components/findings/send-to-jira-modal.tsx b/ui/components/findings/send-to-jira-modal.tsx index cee45c9464..aa6774da45 100644 --- a/ui/components/findings/send-to-jira-modal.tsx +++ b/ui/components/findings/send-to-jira-modal.tsx @@ -12,13 +12,13 @@ import { pollJiraDispatchTask, sendFindingToJira, } from "@/actions/integrations/jira-dispatch"; +import { useToast } from "@/components/shadcn"; +import { CustomBanner } from "@/components/shadcn/custom/custom-banner"; +import { Form, FormField, FormMessage } from "@/components/shadcn/form"; +import { FormButtons } from "@/components/shadcn/form/form-buttons"; import { Modal } from "@/components/shadcn/modal"; import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select"; import { Skeleton } from "@/components/shadcn/skeleton/skeleton"; -import { useToast } from "@/components/ui"; -import { CustomBanner } from "@/components/ui/custom/custom-banner"; -import { Form, FormField, FormMessage } from "@/components/ui/form"; -import { FormButtons } from "@/components/ui/form/form-buttons"; import { IntegrationProps } from "@/types/integrations"; interface SendToJiraModalProps { diff --git a/ui/components/findings/table/column-finding-groups.test.tsx b/ui/components/findings/table/column-finding-groups.test.tsx index 3daf96987c..5a8ec51e56 100644 --- a/ui/components/findings/table/column-finding-groups.test.tsx +++ b/ui/components/findings/table/column-finding-groups.test.tsx @@ -18,7 +18,8 @@ vi.mock("next/navigation", () => ({ useSearchParams: () => new URLSearchParams(), })); -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), Button: ({ children, ...props }: { children: ReactNode }) => ( ), @@ -43,7 +44,7 @@ vi.mock("@/components/shadcn", () => ({ ), })); -vi.mock("@/components/ui/table", () => ({ +vi.mock("@/components/shadcn/table", () => ({ DataTableColumnHeader: ({ title, }: { diff --git a/ui/components/findings/table/column-finding-groups.tsx b/ui/components/findings/table/column-finding-groups.tsx index 79b584edcc..f200a4ed39 100644 --- a/ui/components/findings/table/column-finding-groups.tsx +++ b/ui/components/findings/table/column-finding-groups.tsx @@ -4,16 +4,16 @@ import { ColumnDef, RowSelectionState } from "@tanstack/react-table"; import { ChevronRight } from "lucide-react"; import { Checkbox } from "@/components/shadcn"; +import { + DataTableColumnHeader, + SeverityBadge, + StatusFindingBadge, +} from "@/components/shadcn/table"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/shadcn/tooltip"; -import { - DataTableColumnHeader, - SeverityBadge, - StatusFindingBadge, -} from "@/components/ui/table"; import { cn } from "@/lib"; import { canDrillDownFindingGroup, diff --git a/ui/components/findings/table/column-finding-resources.test.tsx b/ui/components/findings/table/column-finding-resources.test.tsx index bb43873da3..877d83c186 100644 --- a/ui/components/findings/table/column-finding-resources.test.tsx +++ b/ui/components/findings/table/column-finding-resources.test.tsx @@ -8,13 +8,14 @@ import type { import { describe, expect, it, vi } from "vitest"; // CustomLink pulls the "@/lib" barrel (and next-auth with it) into the unit env. -vi.mock("@/components/ui/custom/custom-link", () => ({ +vi.mock("@/components/shadcn/custom/custom-link", () => ({ CustomLink: ({ href, children }: { href: string; children: ReactNode }) => ( {children} ), })); -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), Button: ({ children, ...props }: ButtonHTMLAttributes) => ( ), @@ -131,7 +132,7 @@ vi.mock("@/components/shadcn/tooltip", () => ({ TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}, })); -vi.mock("@/components/ui/entities", () => ({ +vi.mock("@/components/shadcn/entities", () => ({ DateWithTime: ({ dateTime, inline, @@ -141,7 +142,7 @@ vi.mock("@/components/ui/entities", () => ({ }) => , })); -vi.mock("@/components/ui/entities/entity-info", () => ({ +vi.mock("@/components/shadcn/entities/entity-info", () => ({ EntityInfo: ({ entityAlias, entityId, @@ -156,17 +157,17 @@ vi.mock("@/components/ui/entities/entity-info", () => ({ ), })); -vi.mock("@/components/ui/table", () => ({ +vi.mock("@/components/shadcn/table", () => ({ SeverityBadge: ({ severity }: { severity: string }) => ( {severity} ), })); -vi.mock("@/components/ui/table/data-table-column-header", () => ({ +vi.mock("@/components/shadcn/table/data-table-column-header", () => ({ DataTableColumnHeader: ({ title }: { title: string }) => {title}, })); -vi.mock("@/components/ui/table/status-finding-badge", () => ({ +vi.mock("@/components/shadcn/table/status-finding-badge", () => ({ StatusFindingBadge: ({ status }: { status: string }) => {status}, })); diff --git a/ui/components/findings/table/column-finding-resources.tsx b/ui/components/findings/table/column-finding-resources.tsx index 7ab2cb01ff..255abea929 100644 --- a/ui/components/findings/table/column-finding-resources.tsx +++ b/ui/components/findings/table/column-finding-resources.tsx @@ -12,16 +12,16 @@ import { ActionDropdown, ActionDropdownItem, } from "@/components/shadcn/dropdown"; +import { DateWithTime } from "@/components/shadcn/entities"; +import { EntityInfo } from "@/components/shadcn/entities/entity-info"; import { InfoField } from "@/components/shadcn/info-field/info-field"; import { Spinner } from "@/components/shadcn/spinner/spinner"; -import { DateWithTime } from "@/components/ui/entities"; -import { EntityInfo } from "@/components/ui/entities/entity-info"; -import { SeverityBadge } from "@/components/ui/table"; -import { DataTableColumnHeader } from "@/components/ui/table/data-table-column-header"; +import { SeverityBadge } from "@/components/shadcn/table"; +import { DataTableColumnHeader } from "@/components/shadcn/table/data-table-column-header"; import { type FindingStatus, StatusFindingBadge, -} from "@/components/ui/table/status-finding-badge"; +} from "@/components/shadcn/table/status-finding-badge"; import { getFailingForLabel } from "@/lib/date-utils"; import { FindingResourceRow } from "@/types"; import type { diff --git a/ui/components/findings/table/column-standalone-findings.test.tsx b/ui/components/findings/table/column-standalone-findings.test.tsx index 7b9a2afe8c..fdd7ec0c3e 100644 --- a/ui/components/findings/table/column-standalone-findings.test.tsx +++ b/ui/components/findings/table/column-standalone-findings.test.tsx @@ -6,7 +6,7 @@ vi.mock("next/navigation", () => ({ })); // CustomLink pulls the "@/lib" barrel (and next-auth with it) into the unit env. -vi.mock("@/components/ui/custom/custom-link", () => ({ +vi.mock("@/components/shadcn/custom/custom-link", () => ({ CustomLink: ({ href, children }: { href: string; children: ReactNode }) => ( {children} ), @@ -47,14 +47,14 @@ vi.mock("@/components/shadcn/spinner/spinner", () => ({ Spinner: () => null, })); -vi.mock("@/components/ui/entities", () => ({ +vi.mock("@/components/shadcn/entities", () => ({ DateWithTime: ({ dateTime }: { dateTime: string | null }) => ( ), EntityInfo: () => null, })); -vi.mock("@/components/ui/table", () => ({ +vi.mock("@/components/shadcn/table", () => ({ DataTableColumnHeader: ({ title }: { title: string }) => {title}, SeverityBadge: ({ severity }: { severity: string }) => ( {severity} diff --git a/ui/components/findings/table/column-standalone-findings.tsx b/ui/components/findings/table/column-standalone-findings.tsx index 7a7c8bb8b4..75887ee9b9 100644 --- a/ui/components/findings/table/column-standalone-findings.tsx +++ b/ui/components/findings/table/column-standalone-findings.tsx @@ -3,12 +3,12 @@ import { ColumnDef } from "@tanstack/react-table"; import { Container } from "lucide-react"; -import { DateWithTime, EntityInfo } from "@/components/ui/entities"; +import { DateWithTime, EntityInfo } from "@/components/shadcn/entities"; import { DataTableColumnHeader, SeverityBadge, StatusFindingBadge, -} from "@/components/ui/table"; +} from "@/components/shadcn/table"; import { getRegionFlag } from "@/lib/region-flags"; import { getOptionalText } from "@/lib/utils"; import { FindingProps, ProviderType } from "@/types"; diff --git a/ui/components/findings/table/delta-indicator.tsx b/ui/components/findings/table/delta-indicator.tsx index 7f79d1ede2..9bd2392210 100644 --- a/ui/components/findings/table/delta-indicator.tsx +++ b/ui/components/findings/table/delta-indicator.tsx @@ -19,9 +19,9 @@ export const DeltaIndicator = ({ delta }: DeltaIndicatorProps) => { className={cn( "h-2 w-2 min-w-2 cursor-pointer rounded-full", delta === "new" - ? "bg-system-severity-high" + ? "bg-bg-data-high" : delta === "changed" - ? "bg-system-severity-low" + ? "bg-bg-data-low" : "bg-text-neutral-tertiary", )} /> diff --git a/ui/components/findings/table/finding-note-modal.test.tsx b/ui/components/findings/table/finding-note-modal.test.tsx index eddb893333..a2546fe47b 100644 --- a/ui/components/findings/table/finding-note-modal.test.tsx +++ b/ui/components/findings/table/finding-note-modal.test.tsx @@ -10,7 +10,7 @@ vi.mock("@/components/icons/providers-badge/provider-type-icon", () => ({ })); // CustomLink pulls the "@/lib" barrel (and next-auth with it) into the unit env. -vi.mock("@/components/ui/custom/custom-link", () => ({ +vi.mock("@/components/shadcn/custom/custom-link", () => ({ CustomLink: ({ href, children }: { href: string; children: ReactNode }) => ( {children} ), diff --git a/ui/components/findings/table/finding-triage-cells.test.tsx b/ui/components/findings/table/finding-triage-cells.test.tsx index 74fd48764c..df9052b51a 100644 --- a/ui/components/findings/table/finding-triage-cells.test.tsx +++ b/ui/components/findings/table/finding-triage-cells.test.tsx @@ -31,7 +31,7 @@ vi.mock("next/navigation", () => ({ })); // CustomLink pulls the "@/lib" barrel (and next-auth with it) into the unit env. -vi.mock("@/components/ui/custom/custom-link", () => ({ +vi.mock("@/components/shadcn/custom/custom-link", () => ({ CustomLink: ({ href, children }: { href: string; children: ReactNode }) => ( {children} ), diff --git a/ui/components/findings/table/findings-group-drill-down.tsx b/ui/components/findings/table/findings-group-drill-down.tsx index 8082027e28..0b96a9657e 100644 --- a/ui/components/findings/table/findings-group-drill-down.tsx +++ b/ui/components/findings/table/findings-group-drill-down.tsx @@ -20,8 +20,8 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table"; -import { SeverityBadge, StatusFindingBadge } from "@/components/ui/table"; +} from "@/components/shadcn/table"; +import { SeverityBadge, StatusFindingBadge } from "@/components/shadcn/table"; import { useFindingGroupResourceState } from "@/hooks/use-finding-group-resource-state"; import { cn, hasHistoricalFindingFilter } from "@/lib"; import { @@ -125,7 +125,7 @@ export function FindingsGroupDrillDown({ >
          diff --git a/ui/components/findings/table/findings-group-table.test.tsx b/ui/components/findings/table/findings-group-table.test.tsx index 411f7a64f7..3d31ac0fb2 100644 --- a/ui/components/findings/table/findings-group-table.test.tsx +++ b/ui/components/findings/table/findings-group-table.test.tsx @@ -12,7 +12,7 @@ vi.mock("next/navigation", () => ({ usePathname: () => "/findings", })); -vi.mock("@/components/ui/table", () => ({ +vi.mock("@/components/shadcn/table", () => ({ DataTable: ({ data, toolbarRightContent, diff --git a/ui/components/findings/table/findings-group-table.tsx b/ui/components/findings/table/findings-group-table.tsx index 86de935873..10eeff0b62 100644 --- a/ui/components/findings/table/findings-group-table.tsx +++ b/ui/components/findings/table/findings-group-table.tsx @@ -7,7 +7,7 @@ import { Suspense, useRef, useState } from "react"; import { resolveFindingIdsByVisibleGroupResources } from "@/actions/findings/findings-by-resource"; import { CustomCheckboxMutedFindings } from "@/components/filters/custom-checkbox-muted-findings"; import { OnboardingTrigger, PageReady } from "@/components/onboarding"; -import { DataTable } from "@/components/ui/table"; +import { DataTable } from "@/components/shadcn/table"; import { canDrillDownFindingGroup } from "@/lib/findings-groups"; import { getFlowById } from "@/lib/onboarding"; import { createExploreFindingsTourStepHandlers } from "@/lib/tours/explore-findings.tour"; diff --git a/ui/components/findings/table/inline-resource-container.tsx b/ui/components/findings/table/inline-resource-container.tsx index 15589d726c..91c4f3b225 100644 --- a/ui/components/findings/table/inline-resource-container.tsx +++ b/ui/components/findings/table/inline-resource-container.tsx @@ -15,7 +15,7 @@ import { } from "@/actions/findings"; import { Skeleton } from "@/components/shadcn/skeleton/skeleton"; import { LoadingState } from "@/components/shadcn/spinner/loading-state"; -import { TableCell, TableRow } from "@/components/ui/table"; +import { TableCell, TableRow } from "@/components/shadcn/table"; import { useFindingGroupResourceState } from "@/hooks/use-finding-group-resource-state"; import { useScrollHint } from "@/hooks/use-scroll-hint"; import { cn } from "@/lib/utils"; diff --git a/ui/components/findings/table/notification-indicator.test.tsx b/ui/components/findings/table/notification-indicator.test.tsx index 093db2f3c6..9c35e3a22f 100644 --- a/ui/components/findings/table/notification-indicator.test.tsx +++ b/ui/components/findings/table/notification-indicator.test.tsx @@ -23,7 +23,8 @@ vi.mock("@/components/icons", () => ({ ), })); -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), Button: ({ children, asChild, diff --git a/ui/components/findings/table/notification-indicator.tsx b/ui/components/findings/table/notification-indicator.tsx index 1691694816..d5b212a3bd 100644 --- a/ui/components/findings/table/notification-indicator.tsx +++ b/ui/components/findings/table/notification-indicator.tsx @@ -101,9 +101,7 @@ function DeltaIndicator({
          diff --git a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.test.tsx b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.test.tsx index d413b8ac08..afedde6c55 100644 --- a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.test.tsx +++ b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.test.tsx @@ -64,11 +64,12 @@ vi.mock("next/link", () => ({ })); // Mock the entire shadcn barrel to avoid auth import chain -vi.mock("@/components/shadcn", () => { +vi.mock("@/components/shadcn", async (importOriginal) => { const Passthrough = ({ children }: { children?: ReactNode }) => ( <>{children} ); return { + ...(await importOriginal>()), Badge: ({ children, className, @@ -129,7 +130,8 @@ vi.mock("@/components/shadcn", () => { }; }); -vi.mock("@/components/shadcn/card/card", () => ({ +vi.mock("@/components/shadcn/card/card", async (importOriginal) => ({ + ...(await importOriginal>()), Card: ({ children, variant }: { children: ReactNode; variant?: string }) => (
          {children} @@ -272,7 +274,7 @@ vi.mock("@/components/icons/services/IconServices", () => ({ JiraIcon: () => null, })); -vi.mock("@/components/ui/code-snippet/code-snippet", () => ({ +vi.mock("@/components/shadcn/code-snippet/code-snippet", () => ({ CodeSnippet: ({ value, formatter, @@ -291,11 +293,11 @@ vi.mock("@/components/ui/code-snippet/code-snippet", () => ({ ), })); -vi.mock("@/components/ui/entities/date-with-time", () => ({ +vi.mock("@/components/shadcn/entities/date-with-time", () => ({ DateWithTime: ({ dateTime }: { dateTime: string }) => {dateTime}, })); -vi.mock("@/components/ui/entities/entity-info", () => ({ +vi.mock("@/components/shadcn/entities/entity-info", () => ({ EntityInfo: ({ nameAction, idAction, @@ -313,7 +315,7 @@ vi.mock("@/components/ui/entities/entity-info", () => ({ ) : null, })); -vi.mock("@/components/ui/table", () => ({ +vi.mock("@/components/shadcn/table", () => ({ Table: ({ children }: { children: ReactNode }) => {children}
          , TableBody: ({ children }: { children: ReactNode }) => ( {children} @@ -332,13 +334,13 @@ vi.mock("@/components/ui/table", () => ({ ), })); -vi.mock("@/components/ui/table/severity-badge", () => ({ +vi.mock("@/components/shadcn/table/severity-badge", () => ({ SeverityBadge: ({ severity }: { severity: string }) => ( {severity} ), })); -vi.mock("@/components/ui/table/status-finding-badge", () => ({ +vi.mock("@/components/shadcn/table/status-finding-badge", () => ({ FindingStatus: {}, StatusFindingBadge: ({ status }: { status: string }) => {status}, })); diff --git a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx index 64feea3b60..af7e38acc3 100644 --- a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx +++ b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx @@ -36,12 +36,28 @@ import { TabsTrigger, } from "@/components/shadcn"; import { Card } from "@/components/shadcn/card/card"; +import { CodeSnippet } from "@/components/shadcn/code-snippet/code-snippet"; import { ActionDropdown, ActionDropdownItem, } from "@/components/shadcn/dropdown"; +import { DateWithTime } from "@/components/shadcn/entities/date-with-time"; +import { EntityInfo } from "@/components/shadcn/entities/entity-info"; import { Skeleton } from "@/components/shadcn/skeleton/skeleton"; import { LoadingState } from "@/components/shadcn/spinner/loading-state"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/shadcn/table"; +import { SeverityBadge } from "@/components/shadcn/table/severity-badge"; +import { + type FindingStatus, + StatusFindingBadge, +} from "@/components/shadcn/table/status-finding-badge"; import { Tooltip, TooltipContent, @@ -55,22 +71,6 @@ import { type QueryEditorLanguage, } from "@/components/shared/query-code-editor"; import { ResourceMetadataPanel } from "@/components/shared/resource-metadata-panel"; -import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; -import { DateWithTime } from "@/components/ui/entities/date-with-time"; -import { EntityInfo } from "@/components/ui/entities/entity-info"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { SeverityBadge } from "@/components/ui/table/severity-badge"; -import { - type FindingStatus, - StatusFindingBadge, -} from "@/components/ui/table/status-finding-badge"; import { getFailingForLabel } from "@/lib/date-utils"; import { formatDuration } from "@/lib/date-utils"; import { shouldRefreshAfterTriageUpdate } from "@/lib/finding-triage"; diff --git a/ui/components/findings/table/skeleton-table-findings.tsx b/ui/components/findings/table/skeleton-table-findings.tsx index 79654d554c..98fd17ee65 100644 --- a/ui/components/findings/table/skeleton-table-findings.tsx +++ b/ui/components/findings/table/skeleton-table-findings.tsx @@ -58,7 +58,7 @@ export const SkeletonTableFindings = () => { const rows = 10; return ( -
          +
          {/* Toolbar: Search + Total entries */}
          {/* Search icon button */} diff --git a/ui/components/graphs/donut-chart.tsx b/ui/components/graphs/donut-chart.tsx index 796e2d9bc2..0111a2936c 100644 --- a/ui/components/graphs/donut-chart.tsx +++ b/ui/components/graphs/donut-chart.tsx @@ -11,7 +11,7 @@ import { Tooltip, } from "recharts"; -import { ChartConfig, ChartContainer } from "@/components/ui/chart/Chart"; +import { ChartConfig, ChartContainer } from "@/components/shadcn/chart/Chart"; import { ChartLegend } from "./shared/chart-legend"; import { DonutDataPoint } from "./types"; diff --git a/ui/components/graphs/line-chart.tsx b/ui/components/graphs/line-chart.tsx index 9b9b676f18..eab0551bb6 100644 --- a/ui/components/graphs/line-chart.tsx +++ b/ui/components/graphs/line-chart.tsx @@ -15,7 +15,7 @@ import { ChartConfig, ChartContainer, ChartTooltip, -} from "@/components/ui/chart/Chart"; +} from "@/components/shadcn/chart/Chart"; import { AlertPill } from "./shared/alert-pill"; import { ChartLegend } from "./shared/chart-legend"; diff --git a/ui/components/graphs/map-region-filter.tsx b/ui/components/graphs/map-region-filter.tsx index 3b483d1c62..3b779454a8 100644 --- a/ui/components/graphs/map-region-filter.tsx +++ b/ui/components/graphs/map-region-filter.tsx @@ -6,7 +6,7 @@ import { SelectItem, SelectTrigger, SelectValue, -} from "../ui/select/Select"; +} from "@/components/shadcn/select/select"; interface MapRegionFilterProps { regions: string[]; diff --git a/ui/components/graphs/radar-chart.tsx b/ui/components/graphs/radar-chart.tsx index 6acd773e39..4fe807b6fd 100644 --- a/ui/components/graphs/radar-chart.tsx +++ b/ui/components/graphs/radar-chart.tsx @@ -12,7 +12,7 @@ import { ChartConfig, ChartContainer, ChartTooltip, -} from "@/components/ui/chart/Chart"; +} from "@/components/shadcn/chart/Chart"; import { AlertPill } from "./shared/alert-pill"; import { RadarDataPoint } from "./types"; diff --git a/ui/components/graphs/sankey-chart.tsx b/ui/components/graphs/sankey-chart.tsx index f65647d73e..a574954506 100644 --- a/ui/components/graphs/sankey-chart.tsx +++ b/ui/components/graphs/sankey-chart.tsx @@ -627,7 +627,7 @@ export function SankeyChart({
          )} {zeroDataProviders.length > 0 && ( -
          +

          Providers with no failed findings

          diff --git a/ui/components/icons/providers-badge/provider-type-icon.test.tsx b/ui/components/icons/providers-badge/provider-type-icon.test.tsx index 96b4f5f4d4..f50a71ae46 100644 --- a/ui/components/icons/providers-badge/provider-type-icon.test.tsx +++ b/ui/components/icons/providers-badge/provider-type-icon.test.tsx @@ -29,7 +29,8 @@ vi.mock("@/components/icons/providers-badge", () => ({ })); // Render the tooltip pieces inline so the hover content is queryable in jsdom. -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), Badge: ({ children }: { children: React.ReactNode }) => ( {children} ), diff --git a/ui/components/icons/prowler/ProwlerIcons.tsx b/ui/components/icons/prowler/ProwlerIcons.tsx index 4a616553a4..1beb80ae25 100644 --- a/ui/components/icons/prowler/ProwlerIcons.tsx +++ b/ui/components/icons/prowler/ProwlerIcons.tsx @@ -10,7 +10,7 @@ export const ProwlerExtended: React.FC = ({ }) => { return ( = ({ ...props }) => (
          -

          +

          Need help configuring your Jira integration?

          - + {/* S3 Configuration */}
          @@ -421,7 +421,7 @@ export const S3IntegrationForm = ({ >
          -

          +

          Need help configuring your Amazon S3 integration?

          ({ + deleteSamlConfig: vi.fn(), +})); + +vi.mock("./saml-config-form", () => ({ + SamlConfigForm: () =>
          SAML form
          , +})); + +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), + useToast: () => ({ toast: vi.fn() }), +})); + +describe("SamlIntegrationCard", () => { + it("shows the disabled status as a header badge without a status label", () => { + // When + render(); + + // Then + const title = screen.getByRole("heading", { + name: "SAML SSO Integration", + }); + const header = title.closest('[data-slot="card-header"]'); + + expect(header).toBeInTheDocument(); + expect(header).not.toHaveTextContent("Status:"); + expect(within(header as HTMLElement).getByText("Disabled")).toHaveAttribute( + "data-slot", + "badge", + ); + }); + + it("shows the enabled status as a success header badge", () => { + // When + render(); + + // Then + const title = screen.getByRole("heading", { + name: "SAML SSO Integration", + }); + const header = title.closest('[data-slot="card-header"]'); + const badge = within(header as HTMLElement).getByText("Enabled"); + + expect(header).not.toHaveTextContent("Status:"); + expect(badge).toHaveAttribute("data-slot", "badge"); + expect(badge).toHaveClass("bg-bg-pass-secondary"); + }); +}); diff --git a/ui/components/integrations/saml/saml-integration-card.tsx b/ui/components/integrations/saml/saml-integration-card.tsx index f1b5c2c966..87bee0f55d 100644 --- a/ui/components/integrations/saml/saml-integration-card.tsx +++ b/ui/components/integrations/saml/saml-integration-card.tsx @@ -1,15 +1,21 @@ "use client"; -import { CheckIcon, Trash2Icon } from "lucide-react"; +import { Trash2Icon } from "lucide-react"; import { useState } from "react"; import { deleteSamlConfig } from "@/actions/integrations"; -import { Button } from "@/components/shadcn"; +import { + Badge, + Button, + Card, + CardAction, + CardContent, + CardHeader, + 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 { Card, CardContent, CardHeader } from "../../shadcn"; import { SamlConfigForm } from "./saml-config-form"; export const SamlIntegrationCard = ({ samlConfig }: { samlConfig?: any }) => { @@ -18,6 +24,7 @@ export const SamlIntegrationCard = ({ samlConfig }: { samlConfig?: any }) => { const [isDeleting, setIsDeleting] = useState(false); const { toast } = useToast(); const id = samlConfig?.id; + const statusLabel = id ? "Enabled" : "Disabled"; const handleRemoveSaml = async () => { if (!id) return; @@ -72,7 +79,7 @@ export const SamlIntegrationCard = ({ samlConfig }: { samlConfig?: any }) => { size="md" >
          -

          +

          Are you sure you want to remove the SAML SSO configuration? Users will no longer be able to sign in using SAML.

          @@ -100,13 +107,10 @@ export const SamlIntegrationCard = ({ samlConfig }: { samlConfig?: any }) => {
          - +
          -
          -

          SAML SSO Integration

          - {id && } -
          +

          SAML SSO Integration

          {id ? ( "SAML Single Sign-On is enabled for this organization" @@ -120,30 +124,25 @@ export const SamlIntegrationCard = ({ samlConfig }: { samlConfig?: any }) => { )}

          + + {statusLabel} +
          -
          -
          - Status: - - {id ? "Enabled" : "Disabled"} - -
          -
          - + {id && ( + - {id && ( - - )} -
          + )}
          diff --git a/ui/components/integrations/security-hub/security-hub-integration-card.tsx b/ui/components/integrations/security-hub/security-hub-integration-card.tsx index bce91812f4..4003f8c286 100644 --- a/ui/components/integrations/security-hub/security-hub-integration-card.tsx +++ b/ui/components/integrations/security-hub/security-hub-integration-card.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { AWSSecurityHubIcon } from "@/components/icons/services/IconServices"; import { Button } from "@/components/shadcn"; -import { CustomLink } from "@/components/ui/custom/custom-link"; +import { CustomLink } from "@/components/shadcn/custom/custom-link"; import { Card, CardContent, CardHeader } from "../../shadcn"; diff --git a/ui/components/integrations/security-hub/security-hub-integration-form.tsx b/ui/components/integrations/security-hub/security-hub-integration-form.tsx index 1c3b436832..b7fac10706 100644 --- a/ui/components/integrations/security-hub/security-hub-integration-form.tsx +++ b/ui/components/integrations/security-hub/security-hub-integration-form.tsx @@ -1,8 +1,5 @@ "use client"; -import { Checkbox } from "@heroui/checkbox"; -import { Divider } from "@heroui/divider"; -import { Radio, RadioGroup } from "@heroui/radio"; import { zodResolver } from "@hookform/resolvers/zod"; import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react"; import { useSession } from "next-auth/react"; @@ -15,16 +12,21 @@ import { ProviderTypeIcon, } from "@/components/icons/providers-badge/provider-type-icon"; import { AWSRoleCredentialsForm } from "@/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form"; -import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select"; -import { useToast } from "@/components/ui"; -import { CustomLink } from "@/components/ui/custom/custom-link"; +import { Checkbox, Separator } from "@/components/shadcn"; +import { useToast } from "@/components/shadcn"; +import { CustomLink } from "@/components/shadcn/custom/custom-link"; import { Form, FormControl, FormField, FormMessage, -} from "@/components/ui/form"; -import { FormButtons } from "@/components/ui/form/form-buttons"; +} from "@/components/shadcn/form"; +import { FormButtons } from "@/components/shadcn/form/form-buttons"; +import { + RadioGroup, + RadioGroupItem, +} from "@/components/shadcn/radio-group/radio-group"; +import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select"; import { getAWSCredentialsTemplateLinks } from "@/lib"; import { AWSCredentialsRole } from "@/types"; import { @@ -289,8 +291,8 @@ export const SecurityHubIntegrationForm = ({ return (
          { form.setValue("use_custom_credentials", value === "custom", { @@ -299,17 +301,29 @@ export const SecurityHubIntegrationForm = ({ }); }} > - - Use provider credentials - - - Use custom credentials - +
          + + +
          +
          + + +
          {useCustomCredentials && ( <> - + } setValue={form.setValue as any} @@ -382,7 +396,7 @@ export const SecurityHubIntegrationForm = ({ )} />
          - + )} @@ -392,16 +406,20 @@ export const SecurityHubIntegrationForm = ({ name="send_only_fails" render={({ field }) => ( - - +
          + + + +
          )} /> @@ -411,14 +429,20 @@ export const SecurityHubIntegrationForm = ({ name="archive_previous_findings" render={({ field }) => ( - - Archive previous findings - +
          + + +
          )} /> @@ -429,17 +453,21 @@ export const SecurityHubIntegrationForm = ({ name="use_custom_credentials" render={({ field }) => ( - - +
          + + + +
          )} /> @@ -529,7 +557,7 @@ export const SecurityHubIntegrationForm = ({ >
          -

          +

          Need help configuring your AWS Security Hub integration?

          {connectionStatus.label || diff --git a/ui/components/integrations/shared/integration-skeleton.tsx b/ui/components/integrations/shared/integration-skeleton.tsx index 245085ca70..469dbb6f22 100644 --- a/ui/components/integrations/shared/integration-skeleton.tsx +++ b/ui/components/integrations/shared/integration-skeleton.tsx @@ -1,9 +1,9 @@ "use client"; -import { Card, CardBody, CardHeader } from "@heroui/card"; -import { Skeleton } from "@heroui/skeleton"; import { ReactNode } from "react"; +import { Card, CardContent, CardHeader, Skeleton } from "@/components/shadcn"; + interface IntegrationSkeletonProps { variant?: "main" | "manager"; count?: number; @@ -21,8 +21,8 @@ export const IntegrationSkeleton = ({ }: IntegrationSkeletonProps) => { if (variant === "main") { return ( - - + +
          {icon} @@ -39,12 +39,12 @@ export const IntegrationSkeleton = ({
          - +
          -
          +
          ); } @@ -53,8 +53,8 @@ export const IntegrationSkeleton = ({ return (
          {Array.from({ length: count }).map((_, index) => ( - - + +
          {icon} @@ -70,7 +70,7 @@ export const IntegrationSkeleton = ({
          - +
          {/* Region chips skeleton */}
          @@ -89,7 +89,7 @@ export const IntegrationSkeleton = ({
          - +
          ))}
          diff --git a/ui/components/integrations/shared/link-card.tsx b/ui/components/integrations/shared/link-card.tsx index 5721f3a3b5..ceb74d6a1b 100644 --- a/ui/components/integrations/shared/link-card.tsx +++ b/ui/components/integrations/shared/link-card.tsx @@ -4,7 +4,7 @@ import { ExternalLinkIcon, LucideIcon } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/shadcn"; -import { CustomLink } from "@/components/ui/custom/custom-link"; +import { CustomLink } from "@/components/shadcn/custom/custom-link"; import { Card, CardContent, CardHeader } from "../../shadcn"; @@ -34,7 +34,7 @@ export const LinkCard = ({
          -
          +
          diff --git a/ui/components/invitations/forms/delete-form.tsx b/ui/components/invitations/forms/delete-form.tsx index 097ad9620d..58618b9dc6 100644 --- a/ui/components/invitations/forms/delete-form.tsx +++ b/ui/components/invitations/forms/delete-form.tsx @@ -8,8 +8,8 @@ import * as z from "zod"; import { revokeInvite } from "@/actions/invitations/invitation"; import { DeleteIcon } from "@/components/icons"; import { Button } from "@/components/shadcn"; -import { useToast } from "@/components/ui"; -import { Form } from "@/components/ui/form"; +import { useToast } from "@/components/shadcn"; +import { Form } from "@/components/shadcn/form"; const formSchema = z.object({ invitationId: z.string(), diff --git a/ui/components/invitations/forms/edit-form.tsx b/ui/components/invitations/forms/edit-form.tsx index 57f0717313..b8bfaf6f70 100644 --- a/ui/components/invitations/forms/edit-form.tsx +++ b/ui/components/invitations/forms/edit-form.tsx @@ -5,6 +5,9 @@ import { Controller, useForm } from "react-hook-form"; import * as z from "zod"; import { updateInvite } from "@/actions/invitations/invitation"; +import { useToast } from "@/components/shadcn"; +import { CustomInput } from "@/components/shadcn/custom"; +import { Form, FormButtons } from "@/components/shadcn/form"; import { Select, SelectContent, @@ -12,9 +15,6 @@ import { SelectTrigger, SelectValue, } from "@/components/shadcn/select/select"; -import { useToast } from "@/components/ui"; -import { CustomInput } from "@/components/ui/custom"; -import { Form, FormButtons } from "@/components/ui/form"; import { editInviteFormSchema } from "@/types"; import { Card, CardContent } from "../../shadcn"; @@ -103,14 +103,14 @@ export const EditForm = ({ > -
          +
          Email: {invitationEmail}
          -
          +
          Role: diff --git a/ui/components/invitations/invitation-details.tsx b/ui/components/invitations/invitation-details.tsx index 0cc8e2b919..259a1de0a4 100644 --- a/ui/components/invitations/invitation-details.tsx +++ b/ui/components/invitations/invitation-details.tsx @@ -2,11 +2,12 @@ import Link from "next/link"; +import { CodeSnippet } from "@/components/shadcn/code-snippet/code-snippet"; +import { DateWithTime } from "@/components/shadcn/entities"; + import { AddIcon } from "../icons"; import { Button, Card, CardContent, CardHeader } from "../shadcn"; import { Separator } from "../shadcn/separator/separator"; -import { CodeSnippet } from "../ui/code-snippet/code-snippet"; -import { DateWithTime } from "../ui/entities"; interface InvitationDetailsProps { attributes: { @@ -39,7 +40,7 @@ const InfoField = ({ {label}
          - + {children}
          diff --git a/ui/components/invitations/table/column-invitations.tsx b/ui/components/invitations/table/column-invitations.tsx index 2af64ef6fd..628f0c2788 100644 --- a/ui/components/invitations/table/column-invitations.tsx +++ b/ui/components/invitations/table/column-invitations.tsx @@ -2,8 +2,8 @@ import { ColumnDef } from "@tanstack/react-table"; -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 { InvitationProps } from "@/types"; import { DataTableRowActions } from "./data-table-row-actions"; diff --git a/ui/components/invitations/table/skeleton-table-invitations.tsx b/ui/components/invitations/table/skeleton-table-invitations.tsx index 1117358e32..2533655a1d 100644 --- a/ui/components/invitations/table/skeleton-table-invitations.tsx +++ b/ui/components/invitations/table/skeleton-table-invitations.tsx @@ -1,37 +1,102 @@ -import React from "react"; - -import { Card } from "@/components/shadcn/card/card"; import { Skeleton } from "@/components/shadcn/skeleton/skeleton"; -export const SkeletonTableInvitation = () => { +const SkeletonTableRow = () => { return ( - - {/* Table headers */} -
          - - - - - - -
          - - {/* Table body */} -
          - {[...Array(10)].map((_, index) => ( -
          - - - - - - -
          - ))} -
          -
          + + {/* Email */} + + + + {/* State - text */} + + + + {/* Role - text */} + + + + {/* Inserted At - date */} + + + + {/* Expires At - date */} + + + + {/* Actions */} + + + + + ); +}; + +export const SkeletonTableInvitation = () => { + const rows = 10; + + return ( +
          + {/* Toolbar: Search + Total entries */} +
          + {/* Search icon button */} + + {/* Total entries */} + +
          + + {/* Table */} + + + + {/* Email */} + + {/* State */} + + {/* Role */} + + {/* Inserted At */} + + {/* Expires At */} + + {/* Actions - empty header */} + + + + {Array.from({ length: rows }).map((_, i) => ( + + ))} + +
          + + + + + + + + + + +
          + + {/* Pagination */} +
          + {/* Rows per page */} +
          + + +
          + {/* Page info + navigation */} +
          + +
          + + + + +
          +
          +
          +
          ); }; diff --git a/ui/components/invitations/workflow/forms/send-invitation-form.tsx b/ui/components/invitations/workflow/forms/send-invitation-form.tsx index 729dc89d28..67e6938d4a 100644 --- a/ui/components/invitations/workflow/forms/send-invitation-form.tsx +++ b/ui/components/invitations/workflow/forms/send-invitation-form.tsx @@ -8,6 +8,9 @@ import * as z from "zod"; import { sendInvite } from "@/actions/invitations/invitation"; import { Button } from "@/components/shadcn"; +import { useToast } from "@/components/shadcn"; +import { CustomInput } from "@/components/shadcn/custom"; +import { Form } from "@/components/shadcn/form"; import { Select, SelectContent, @@ -15,9 +18,6 @@ import { SelectTrigger, SelectValue, } from "@/components/shadcn/select/select"; -import { useToast } from "@/components/ui"; -import { CustomInput } from "@/components/ui/custom"; -import { Form } from "@/components/ui/form"; import { ApiError } from "@/types"; const sendInvitationFormSchema = z.object({ diff --git a/ui/components/invitations/workflow/skeleton-invitation-info.tsx b/ui/components/invitations/workflow/skeleton-invitation-info.tsx index b83568d054..4ab48ddcfc 100644 --- a/ui/components/invitations/workflow/skeleton-invitation-info.tsx +++ b/ui/components/invitations/workflow/skeleton-invitation-info.tsx @@ -1,33 +1,17 @@ -import { Card } from "@heroui/card"; -import { Skeleton } from "@heroui/skeleton"; -import React from "react"; +import { Card, Skeleton } from "@/components/shadcn"; export const SkeletonInvitationInfo = () => { return ( - + {/* Table headers */}
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          + + + + + + +
          {/* Table body */} @@ -37,27 +21,13 @@ export const SkeletonInvitationInfo = () => { key={index} className="flex flex-col items-center justify-between md:flex-row md:gap-4" > - -
          -
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          - -
          -
          + + + + + + +
          ))}
          diff --git a/ui/components/invitations/workflow/vertical-steps.tsx b/ui/components/invitations/workflow/vertical-steps.tsx index 16dd7c9647..17abec63d2 100644 --- a/ui/components/invitations/workflow/vertical-steps.tsx +++ b/ui/components/invitations/workflow/vertical-steps.tsx @@ -1,11 +1,12 @@ "use client"; -import { cn } from "@heroui/theme"; import { useControlledState } from "@react-stately/utils"; import { domAnimation, LazyMotion, m } from "framer-motion"; import type { ComponentProps } from "react"; import React from "react"; +import { cn } from "@/lib/utils"; + export type VerticalStepProps = { className?: string; description?: React.ReactNode; @@ -122,38 +123,37 @@ export const VerticalSteps = React.forwardRef< "[--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:var(--bg-button-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; } @@ -161,7 +161,7 @@ export const VerticalSteps = React.forwardRef< if (!className?.includes("--step-color")) colorsVars.unshift(userColor); if (!className?.includes("--inactive-bar-color")) colorsVars.push( - "[--inactive-bar-color:hsl(var(--heroui-default-300))]", + "[--inactive-bar-color:var(--border-neutral-tertiary)]", ); return colorsVars; @@ -186,7 +186,7 @@ export const VerticalSteps = React.forwardRef< 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)} @@ -197,12 +197,9 @@ export const VerticalSteps = React.forwardRef<
          {step.title}
          {step.description}
          diff --git a/ui/components/invitations/workflow/workflow-send-invite.tsx b/ui/components/invitations/workflow/workflow-send-invite.tsx index 9183c27a8d..879a96b83f 100644 --- a/ui/components/invitations/workflow/workflow-send-invite.tsx +++ b/ui/components/invitations/workflow/workflow-send-invite.tsx @@ -1,10 +1,10 @@ "use client"; -import { Progress } from "@heroui/progress"; -import { Spacer } from "@heroui/spacer"; import { usePathname } from "next/navigation"; import React from "react"; +import { Progress } from "@/components/shadcn/progress"; + import { VerticalSteps } from "./vertical-steps"; const steps = [ @@ -36,24 +36,23 @@ export const WorkflowSendInvite = () => {

          Send invitation

          -

          +

          Follow the steps to send an invitation to the users.

          - +
          +
          + Steps + + {`${currentStep + 1} of ${steps.length}`} + +
          + +
          {/* Desktop: Full vertical steps */}
          @@ -71,13 +70,13 @@ export const WorkflowSendInvite = () => {
          Current: {steps[currentStep]?.title}
          -
          +
          {steps[currentStep]?.description}
          - +
          ); }; diff --git a/ui/components/layout/index.ts b/ui/components/layout/index.ts new file mode 100644 index 0000000000..33b087bb29 --- /dev/null +++ b/ui/components/layout/index.ts @@ -0,0 +1,4 @@ +export * from "./main-layout"; +export * from "./nav-bar"; +export * from "./sidebar"; +export * from "./user-nav"; diff --git a/ui/components/layout/main-layout/index.ts b/ui/components/layout/main-layout/index.ts new file mode 100644 index 0000000000..b816f0d4ee --- /dev/null +++ b/ui/components/layout/main-layout/index.ts @@ -0,0 +1 @@ +export * from "./main-layout"; diff --git a/ui/components/ui/main-layout/main-layout.test.tsx b/ui/components/layout/main-layout/main-layout.test.tsx similarity index 100% rename from ui/components/ui/main-layout/main-layout.test.tsx rename to ui/components/layout/main-layout/main-layout.test.tsx diff --git a/ui/components/ui/main-layout/main-layout.tsx b/ui/components/layout/main-layout/main-layout.tsx similarity index 100% rename from ui/components/ui/main-layout/main-layout.tsx rename to ui/components/layout/main-layout/main-layout.tsx diff --git a/ui/components/layout/nav-bar/index.ts b/ui/components/layout/nav-bar/index.ts new file mode 100644 index 0000000000..0f6da2a562 --- /dev/null +++ b/ui/components/layout/nav-bar/index.ts @@ -0,0 +1,2 @@ +export * from "./navbar"; +export * from "./navbar-client"; diff --git a/ui/components/ui/nav-bar/navbar-client.test.tsx b/ui/components/layout/nav-bar/navbar-client.test.tsx similarity index 98% rename from ui/components/ui/nav-bar/navbar-client.test.tsx rename to ui/components/layout/nav-bar/navbar-client.test.tsx index 43356c3cde..0638837dad 100644 --- a/ui/components/ui/nav-bar/navbar-client.test.tsx +++ b/ui/components/layout/nav-bar/navbar-client.test.tsx @@ -38,7 +38,8 @@ vi.mock("@/components/ThemeSwitch", () => ({ ThemeSwitch: () => , })); -vi.mock("@/components/ui", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), BreadcrumbNavigation: ({ title, titleAction, diff --git a/ui/components/ui/nav-bar/navbar-client.tsx b/ui/components/layout/nav-bar/navbar-client.tsx similarity index 98% rename from ui/components/ui/nav-bar/navbar-client.tsx rename to ui/components/layout/nav-bar/navbar-client.tsx index bdeb879034..e2992b1d11 100644 --- a/ui/components/ui/nav-bar/navbar-client.tsx +++ b/ui/components/layout/nav-bar/navbar-client.tsx @@ -5,13 +5,13 @@ import { usePathname, useRouter } from "next/navigation"; import { ReactNode, Suspense } from "react"; import { + BreadcrumbNavigation, Button, Tooltip, TooltipContent, TooltipTrigger, } from "@/components/shadcn"; import { ThemeSwitch } from "@/components/ThemeSwitch"; -import { BreadcrumbNavigation } from "@/components/ui"; import { useSidebar } from "@/hooks/use-sidebar"; import { getFlowById } from "@/lib/onboarding"; import { isCloud } from "@/lib/shared/env"; diff --git a/ui/components/ui/nav-bar/navbar.tsx b/ui/components/layout/nav-bar/navbar.tsx similarity index 100% rename from ui/components/ui/nav-bar/navbar.tsx rename to ui/components/layout/nav-bar/navbar.tsx diff --git a/ui/components/ui/sidebar/collapsible-menu.tsx b/ui/components/layout/sidebar/collapsible-menu.tsx similarity index 96% rename from ui/components/ui/sidebar/collapsible-menu.tsx rename to ui/components/layout/sidebar/collapsible-menu.tsx index 7937bf940d..27d62a5a3f 100644 --- a/ui/components/ui/sidebar/collapsible-menu.tsx +++ b/ui/components/layout/sidebar/collapsible-menu.tsx @@ -4,18 +4,18 @@ import { ChevronDown } from "lucide-react"; import { usePathname } from "next/navigation"; import { useEffect, useState } from "react"; +import { SubmenuItem } from "@/components/layout/sidebar/submenu-item"; import { Button } from "@/components/shadcn/button/button"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/shadcn/collapsible"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/shadcn/tooltip"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible/collapsible"; -import { SubmenuItem } from "@/components/ui/sidebar/submenu-item"; import { cn } from "@/lib/utils"; import { IconComponent, SubmenuProps } from "@/types"; diff --git a/ui/components/layout/sidebar/index.ts b/ui/components/layout/sidebar/index.ts new file mode 100644 index 0000000000..6fe9b4c4e2 --- /dev/null +++ b/ui/components/layout/sidebar/index.ts @@ -0,0 +1,7 @@ +export * from "./collapsible-menu"; +export * from "./menu"; +export * from "./menu-item"; +export * from "./sheet-menu"; +export * from "./sidebar"; +export * from "./sidebar-toggle"; +export * from "./submenu-item"; diff --git a/ui/components/ui/sidebar/menu-item.tsx b/ui/components/layout/sidebar/menu-item.tsx similarity index 100% rename from ui/components/ui/sidebar/menu-item.tsx rename to ui/components/layout/sidebar/menu-item.tsx diff --git a/ui/components/ui/sidebar/menu.test.tsx b/ui/components/layout/sidebar/menu.test.tsx similarity index 100% rename from ui/components/ui/sidebar/menu.test.tsx rename to ui/components/layout/sidebar/menu.test.tsx diff --git a/ui/components/ui/sidebar/menu.tsx b/ui/components/layout/sidebar/menu.tsx similarity index 95% rename from ui/components/ui/sidebar/menu.tsx rename to ui/components/layout/sidebar/menu.tsx index 76ae3e2765..9be27853f1 100644 --- a/ui/components/ui/sidebar/menu.tsx +++ b/ui/components/layout/sidebar/menu.tsx @@ -1,21 +1,21 @@ "use client"; -import { Divider } from "@heroui/divider"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { LighthouseV2SidebarChat } from "@/app/(prowler)/lighthouse/_components/navigation"; import { InfoIcon, ProwlerShort } from "@/components/icons"; +import { CollapsibleMenu } from "@/components/layout/sidebar/collapsible-menu"; +import { MenuItem } from "@/components/layout/sidebar/menu-item"; +import { Separator } from "@/components/shadcn"; import { Button } from "@/components/shadcn/button/button"; +import { ScrollArea } from "@/components/shadcn/scroll-area/scroll-area"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/shadcn/tooltip"; import { SidebarNavigationModeToggle } from "@/components/sidebar/navigation-mode-toggle"; -import { ScrollArea } from "@/components/ui/scroll-area/scroll-area"; -import { CollapsibleMenu } from "@/components/ui/sidebar/collapsible-menu"; -import { MenuItem } from "@/components/ui/sidebar/menu-item"; import { useAuth } from "@/hooks"; import { useRuntimeConfig } from "@/hooks/use-runtime-config"; import { SIDEBAR_NAVIGATION_MODE, useSidebar } from "@/hooks/use-sidebar"; @@ -172,7 +172,7 @@ export const Menu = ({ isOpen }: { isOpen: boolean }) => { {process.env.NEXT_PUBLIC_PROWLER_RELEASE_VERSION} {process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true" && ( <> - + setIsHover(false)} className="no-scrollbar relative flex h-full flex-col overflow-x-hidden overflow-y-auto px-3 py-6" > - + +
          +
          + +
          +
          diff --git a/ui/components/ui/sidebar/submenu-item.test.tsx b/ui/components/layout/sidebar/submenu-item.test.tsx similarity index 100% rename from ui/components/ui/sidebar/submenu-item.test.tsx rename to ui/components/layout/sidebar/submenu-item.test.tsx diff --git a/ui/components/ui/sidebar/submenu-item.tsx b/ui/components/layout/sidebar/submenu-item.tsx similarity index 100% rename from ui/components/ui/sidebar/submenu-item.tsx rename to ui/components/layout/sidebar/submenu-item.tsx diff --git a/ui/components/layout/user-nav/index.ts b/ui/components/layout/user-nav/index.ts new file mode 100644 index 0000000000..2e600a84a1 --- /dev/null +++ b/ui/components/layout/user-nav/index.ts @@ -0,0 +1 @@ +export * from "./user-nav"; diff --git a/ui/components/ui/user-nav/user-nav.tsx b/ui/components/layout/user-nav/user-nav.tsx similarity index 94% rename from ui/components/ui/user-nav/user-nav.tsx rename to ui/components/layout/user-nav/user-nav.tsx index dd2c2d8247..20c5baf2a3 100644 --- a/ui/components/ui/user-nav/user-nav.tsx +++ b/ui/components/layout/user-nav/user-nav.tsx @@ -4,18 +4,18 @@ import { LogOut } from "lucide-react"; import { useSession } from "next-auth/react"; import { logOut } from "@/actions/auth"; +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/shadcn/avatar/avatar"; import { Button } from "@/components/shadcn/button/button"; +import { CustomLink } from "@/components/shadcn/custom/custom-link"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/shadcn/tooltip"; -import { - Avatar, - AvatarFallback, - AvatarImage, -} from "@/components/ui/avatar/avatar"; -import { CustomLink } from "@/components/ui/custom/custom-link"; export const UserNav = () => { const { data: session } = useSession(); diff --git a/ui/components/lighthouse-v1/ai-elements/actions.tsx b/ui/components/lighthouse-v1/ai-elements/actions.tsx index 900f7409c6..537957a74b 100644 --- a/ui/components/lighthouse-v1/ai-elements/actions.tsx +++ b/ui/components/lighthouse-v1/ai-elements/actions.tsx @@ -37,7 +37,7 @@ export const Action = ({ const button = ( ), diff --git a/ui/components/providers/organizations/org-launch-scan.tsx b/ui/components/providers/organizations/org-launch-scan.tsx index 90711033c6..9339fb1a16 100644 --- a/ui/components/providers/organizations/org-launch-scan.tsx +++ b/ui/components/providers/organizations/org-launch-scan.tsx @@ -14,6 +14,7 @@ import { WizardFooterConfig, } from "@/components/providers/wizard/steps/footer-controls"; import { ScanScheduleFields } from "@/components/scans/schedule/scan-schedule-fields"; +import { ToastAction, useToast } from "@/components/shadcn"; import { Select, SelectContent, @@ -24,7 +25,6 @@ import { import { Spinner } from "@/components/shadcn/spinner/spinner"; import { TreeStatusIcon } from "@/components/shadcn/tree-view/tree-status-icon"; import { UsageLimitMessage } from "@/components/shared/usage-limit-message"; -import { ToastAction, useToast } from "@/components/ui"; import { getActionErrorMessage, hasActionError } from "@/lib/action-errors"; import { buildScheduleUpdatePayload, diff --git a/ui/components/providers/organizations/org-setup-form.tsx b/ui/components/providers/organizations/org-setup-form.tsx index fe6d086d9f..a9852fa202 100644 --- a/ui/components/providers/organizations/org-setup-form.tsx +++ b/ui/components/providers/organizations/org-setup-form.tsx @@ -1,10 +1,9 @@ "use client"; -import { useClipboard } from "@heroui/use-clipboard"; import { zodResolver } from "@hookform/resolvers/zod"; import { Check, Copy, ExternalLink } from "lucide-react"; import { useSession } from "next-auth/react"; -import { FormEvent, useEffect, useState } from "react"; +import { FormEvent, useEffect, useRef, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { z } from "zod"; @@ -19,12 +18,12 @@ import { OrgWizardIntent, } from "@/components/providers/wizard/types"; import { WizardInputField } from "@/components/providers/workflow/forms/fields"; +import { useToast } from "@/components/shadcn"; import { Alert, AlertDescription } from "@/components/shadcn/alert"; import { Button } from "@/components/shadcn/button/button"; import { Checkbox } from "@/components/shadcn/checkbox/checkbox"; +import { Form } from "@/components/shadcn/form"; import { Spinner } from "@/components/shadcn/spinner/spinner"; -import { useToast } from "@/components/ui"; -import { Form } from "@/components/ui/form"; import { getAWSCredentialsTemplateLinks, PROWLER_CF_TEMPLATE_URL, @@ -90,12 +89,29 @@ export function OrgSetupForm({ const stackSetExternalId = session?.tenantId ?? ""; const { organizationId } = useOrgSetupStore(); const { toast } = useToast(); - const { copied: isExternalIdCopied, copy: copyExternalId } = useClipboard({ - timeout: 1500, - }); - const { copied: isTemplateUrlCopied, copy: copyTemplateUrl } = useClipboard({ - timeout: 1500, - }); + const COPY_RESET_TIMEOUT = 1500; + const [isExternalIdCopied, setIsExternalIdCopied] = useState(false); + const [isTemplateUrlCopied, setIsTemplateUrlCopied] = useState(false); + const externalIdCopyTimer = useRef>(undefined); + const templateUrlCopyTimer = useRef>(undefined); + + // Copies text and flips the copied flag back after the timeout, without effects + const copyWithFeedback = ( + text: string, + setCopied: (copied: boolean) => void, + timerRef: { current: ReturnType | undefined }, + ) => { + navigator.clipboard.writeText(text).then(() => { + setCopied(true); + clearTimeout(timerRef.current); + timerRef.current = setTimeout(() => setCopied(false), COPY_RESET_TIMEOUT); + }); + }; + + const copyExternalId = (text: string) => + copyWithFeedback(text, setIsExternalIdCopied, externalIdCopyTimer); + const copyTemplateUrl = (text: string) => + copyWithFeedback(text, setIsTemplateUrlCopied, templateUrlCopyTimer); const [setupPhase, setSetupPhase] = useState(initialPhase); const [isSaving, setIsSaving] = useState(false); const formId = "org-wizard-setup-form"; diff --git a/ui/components/providers/providers-accounts-table.test.tsx b/ui/components/providers/providers-accounts-table.test.tsx index 5be9da5260..8c0a6a1c5d 100644 --- a/ui/components/providers/providers-accounts-table.test.tsx +++ b/ui/components/providers/providers-accounts-table.test.tsx @@ -21,7 +21,7 @@ const { dataTableMockState, getColumnProvidersMock } = vi.hoisted(() => ({ getColumnProvidersMock: vi.fn((..._args: unknown[]) => []), })); -vi.mock("@/components/ui/table", () => ({ +vi.mock("@/components/shadcn/table", () => ({ DataTable: ({ onRowSelectionChange, }: { diff --git a/ui/components/providers/providers-accounts-table.tsx b/ui/components/providers/providers-accounts-table.tsx index ba0dc99c17..256b6f5c50 100644 --- a/ui/components/providers/providers-accounts-table.tsx +++ b/ui/components/providers/providers-accounts-table.tsx @@ -7,7 +7,7 @@ import type { OrgWizardInitialData, ProviderWizardInitialData, } from "@/components/providers/wizard/types"; -import { DataTable } from "@/components/ui/table"; +import { DataTable } from "@/components/shadcn/table"; import { MetaDataProps } from "@/types"; import { isProvidersOrganizationRow, diff --git a/ui/components/providers/providers-filters.test.tsx b/ui/components/providers/providers-filters.test.tsx index 37080b3f77..667039aa10 100644 --- a/ui/components/providers/providers-filters.test.tsx +++ b/ui/components/providers/providers-filters.test.tsx @@ -24,7 +24,7 @@ vi.mock("@/components/filters/clear-filters-button", () => ({ ClearFiltersButton: () => , })); -vi.mock("@/components/ui/entities/entity-info", () => ({ +vi.mock("@/components/shadcn/entities/entity-info", () => ({ EntityInfo: () => null, })); diff --git a/ui/components/providers/providers-filters.tsx b/ui/components/providers/providers-filters.tsx index d488a5849c..29f3607665 100644 --- a/ui/components/providers/providers-filters.tsx +++ b/ui/components/providers/providers-filters.tsx @@ -6,6 +6,7 @@ import type { ReactNode } from "react"; import { ProviderTypeSelector } from "@/app/(prowler)/_overview/_components/provider-type-selector"; import { ClearFiltersButton } from "@/components/filters/clear-filters-button"; import { ProviderGroupSelector } from "@/components/filters/provider-group-selector"; +import { EntityInfo } from "@/components/shadcn/entities/entity-info"; import { MultiSelect, MultiSelectContent, @@ -15,7 +16,6 @@ import { MultiSelectTrigger, MultiSelectValue, } from "@/components/shadcn/select/multiselect"; -import { EntityInfo } from "@/components/ui/entities/entity-info"; import { useUrlFilters } from "@/hooks/use-url-filters"; import { isConnectionStatus, isGroupFilterEntity } from "@/lib/helper-filters"; import { FilterEntity, FilterOption, ProviderEntity } from "@/types"; diff --git a/ui/components/providers/radio-card.tsx b/ui/components/providers/radio-card.tsx index f4b5e13aca..2a15fa6b19 100644 --- a/ui/components/providers/radio-card.tsx +++ b/ui/components/providers/radio-card.tsx @@ -30,15 +30,15 @@ export function RadioCard({ disabled ? "border-border-neutral-primary bg-bg-neutral-tertiary cursor-not-allowed" : selected - ? "border-primary bg-bg-neutral-tertiary cursor-pointer" - : "hover:border-primary border-border-neutral-primary bg-bg-neutral-tertiary cursor-pointer", + ? "border-button-primary bg-bg-neutral-tertiary cursor-pointer" + : "hover:border-button-primary border-border-neutral-primary bg-bg-neutral-tertiary cursor-pointer", )} >
          @@ -53,7 +53,9 @@ export function RadioCard({ {title} diff --git a/ui/components/providers/radio-group-provider.tsx b/ui/components/providers/radio-group-provider.tsx index 6c9930fd83..de8d941f3a 100644 --- a/ui/components/providers/radio-group-provider.tsx +++ b/ui/components/providers/radio-group-provider.tsx @@ -5,6 +5,7 @@ import { Control, Controller } from "react-hook-form"; import { z } from "zod"; import { SearchInput } from "@/components/shadcn"; +import { FormMessage } from "@/components/shadcn/form"; import { cn } from "@/lib/utils"; import { addProviderFormSchema } from "@/types"; @@ -26,7 +27,6 @@ import { OracleCloudProviderBadge, VercelProviderBadge, } from "../icons/providers-badge"; -import { FormMessage } from "../ui/form"; const PROVIDERS = [ { @@ -169,22 +169,22 @@ export const RadioGroupProvider: FC = ({ onClick={() => field.onChange(provider.value)} className={cn( "flex min-h-[72px] w-full items-center gap-4 rounded-lg border px-3 py-2.5 text-left transition-colors", - "focus-visible:border-primary focus-visible:outline-none", + "focus-visible:border-button-primary focus-visible:outline-none", isSelected - ? "border-primary bg-bg-neutral-tertiary" - : "border-border-neutral-primary bg-bg-neutral-tertiary hover:border-primary", + ? "border-button-primary bg-bg-neutral-tertiary" + : "border-border-neutral-primary bg-bg-neutral-tertiary hover:border-button-primary", isInvalid && "border-bg-fail", )} >
          {isSelected && ( -
          +
          )}
          - + {provider.label}
          diff --git a/ui/components/providers/scan-config/manage-scan-config-modal.test.tsx b/ui/components/providers/scan-config/manage-scan-config-modal.test.tsx index 28f4ce7eb7..1c93a65e49 100644 --- a/ui/components/providers/scan-config/manage-scan-config-modal.test.tsx +++ b/ui/components/providers/scan-config/manage-scan-config-modal.test.tsx @@ -15,11 +15,12 @@ vi.mock("@/actions/scan-configurations", () => ({ setScanConfigurationProviders: setScanConfigurationProvidersMock, })); -vi.mock("@/components/ui", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), useToast: () => ({ toast: toastMock }), })); -vi.mock("@/components/ui/custom/custom-link", () => ({ +vi.mock("@/components/shadcn/custom/custom-link", () => ({ CustomLink: ({ children }: { children: React.ReactNode }) => ( {children} ), diff --git a/ui/components/providers/scan-config/manage-scan-config-modal.tsx b/ui/components/providers/scan-config/manage-scan-config-modal.tsx index fe0c395e89..d1d4d6471c 100644 --- a/ui/components/providers/scan-config/manage-scan-config-modal.tsx +++ b/ui/components/providers/scan-config/manage-scan-config-modal.tsx @@ -4,6 +4,8 @@ import { useState } from "react"; import { setScanConfigurationProviders } from "@/actions/scan-configurations"; import { Button } from "@/components/shadcn"; +import { useToast } from "@/components/shadcn"; +import { CustomLink } from "@/components/shadcn/custom/custom-link"; import { Modal } from "@/components/shadcn/modal"; import { Select, @@ -12,8 +14,6 @@ import { SelectTrigger, SelectValue, } from "@/components/shadcn/select/select"; -import { useToast } from "@/components/ui"; -import { CustomLink } from "@/components/ui/custom/custom-link"; import { DOCS_URLS } from "@/lib/external-urls"; import { ScanConfigurationData } from "@/types/scan-configurations"; @@ -158,7 +158,7 @@ function ManageScanConfigForm({ return (
          -

          +

          Choose the scan configuration to apply to{" "} {providerLabel} on its next scan, or leave default. To create or edit configurations, go to{" "} @@ -184,7 +184,7 @@ function ManageScanConfigForm({ ))} -

          +

          Default { " uses Prowler's scan configuration baseline. Read more about it in the " diff --git a/ui/components/providers/table/column-providers.test.tsx b/ui/components/providers/table/column-providers.test.tsx index c2e9abc18c..c126d386ad 100644 --- a/ui/components/providers/table/column-providers.test.tsx +++ b/ui/components/providers/table/column-providers.test.tsx @@ -18,11 +18,11 @@ vi.mock("@/components/shadcn/checkbox/checkbox", () => ({ Checkbox: () => null, })); -vi.mock("@/components/ui/code-snippet/code-snippet", () => ({ +vi.mock("@/components/shadcn/code-snippet/code-snippet", () => ({ CodeSnippet: ({ value }: { value: string }) => {value}, })); -vi.mock("@/components/ui/entities", () => ({ +vi.mock("@/components/shadcn/entities", () => ({ DateWithTime: ({ dateTime }: { dateTime: string | null }) => ( ), @@ -35,15 +35,15 @@ vi.mock("@/components/ui/entities", () => ({ }) => {entityAlias ?? entityId}, })); -vi.mock("@/components/ui/table", () => ({ +vi.mock("@/components/shadcn/table", () => ({ DataTableColumnHeader: ({ title }: { title: string }) => {title}, })); -vi.mock("@/components/ui/table/data-table-expand-all-toggle", () => ({ +vi.mock("@/components/shadcn/table/data-table-expand-all-toggle", () => ({ DataTableExpandAllToggle: () => null, })); -vi.mock("@/components/ui/table/data-table-expandable-cell", () => ({ +vi.mock("@/components/shadcn/table/data-table-expandable-cell", () => ({ DataTableExpandableCell: ({ children }: { children: ReactNode }) => (

          {children}
          ), diff --git a/ui/components/providers/table/column-providers.tsx b/ui/components/providers/table/column-providers.tsx index 6f5a735343..39be1f384f 100644 --- a/ui/components/providers/table/column-providers.tsx +++ b/ui/components/providers/table/column-providers.tsx @@ -9,11 +9,11 @@ import type { } from "@/components/providers/wizard/types"; import { Badge } from "@/components/shadcn"; import { Checkbox } from "@/components/shadcn/checkbox/checkbox"; -import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; -import { DateWithTime, EntityInfo } from "@/components/ui/entities"; -import { DataTableColumnHeader } from "@/components/ui/table"; -import { DataTableExpandAllToggle } from "@/components/ui/table/data-table-expand-all-toggle"; -import { DataTableExpandableCell } from "@/components/ui/table/data-table-expandable-cell"; +import { CodeSnippet } from "@/components/shadcn/code-snippet/code-snippet"; +import { DateWithTime, EntityInfo } from "@/components/shadcn/entities"; +import { DataTableColumnHeader } from "@/components/shadcn/table"; +import { DataTableExpandAllToggle } from "@/components/shadcn/table/data-table-expand-all-toggle"; +import { DataTableExpandableCell } from "@/components/shadcn/table/data-table-expandable-cell"; import { isProvidersOrganizationRow, PROVIDERS_GROUP_KIND, diff --git a/ui/components/providers/table/data-table-row-actions.test.tsx b/ui/components/providers/table/data-table-row-actions.test.tsx index 4a0a471924..93d78c2447 100644 --- a/ui/components/providers/table/data-table-row-actions.test.tsx +++ b/ui/components/providers/table/data-table-row-actions.test.tsx @@ -87,7 +87,8 @@ vi.mock("@/components/scans/schedule/edit-scan-schedule-modal", () => ({ ) : null, })); -vi.mock("@/components/ui", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), useToast: () => ({ toast: vi.fn() }), })); diff --git a/ui/components/providers/table/data-table-row-actions.tsx b/ui/components/providers/table/data-table-row-actions.tsx index d13c05a176..5da2f873e6 100644 --- a/ui/components/providers/table/data-table-row-actions.tsx +++ b/ui/components/providers/table/data-table-row-actions.tsx @@ -26,13 +26,13 @@ import { EditScanScheduleModal, type EditScanScheduleState, } from "@/components/scans/schedule/edit-scan-schedule-modal"; +import { useToast } from "@/components/shadcn"; import { ActionDropdown, ActionDropdownDangerZone, ActionDropdownItem, } from "@/components/shadcn/dropdown"; import { Modal } from "@/components/shadcn/modal"; -import { useToast } from "@/components/ui"; import { runWithConcurrencyLimit } from "@/lib/concurrency"; import { testProviderConnection } from "@/lib/provider-helpers"; import { getScanScheduleCapability } from "@/lib/schedules"; diff --git a/ui/components/providers/table/skeleton-table-provider.tsx b/ui/components/providers/table/skeleton-table-provider.tsx index 1fe557b808..ac99be94bd 100644 --- a/ui/components/providers/table/skeleton-table-provider.tsx +++ b/ui/components/providers/table/skeleton-table-provider.tsx @@ -57,7 +57,7 @@ export const SkeletonTableProviders = () => { const rows = 10; return ( -
          +
          {/* Toolbar: Search + Total entries */}
          diff --git a/ui/components/providers/wizard/steps/launch-step.test.tsx b/ui/components/providers/wizard/steps/launch-step.test.tsx index 110741fd12..caeef23385 100644 --- a/ui/components/providers/wizard/steps/launch-step.test.tsx +++ b/ui/components/providers/wizard/steps/launch-step.test.tsx @@ -34,7 +34,8 @@ vi.mock("@/actions/schedules", () => ({ updateSchedule: updateScheduleMock, })); -vi.mock("@/components/ui", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), ToastAction: ({ children, ...props }: ComponentProps<"button">) => ( ), diff --git a/ui/components/providers/wizard/steps/launch-step.tsx b/ui/components/providers/wizard/steps/launch-step.tsx index cfc5502c23..8c3e96b28b 100644 --- a/ui/components/providers/wizard/steps/launch-step.tsx +++ b/ui/components/providers/wizard/steps/launch-step.tsx @@ -12,6 +12,8 @@ import { } from "@/components/scans/schedule/save-schedule"; import { ScanScheduleFields } from "@/components/scans/schedule/scan-schedule-fields"; import { Field, FieldLabel } from "@/components/shadcn"; +import { ToastAction, useToast } from "@/components/shadcn"; +import { EntityInfo } from "@/components/shadcn/entities"; import { RadioGroup, RadioGroupItem, @@ -23,8 +25,6 @@ import { CloudFeatureBadgeLink, } from "@/components/shared/cloud-feature-badge"; import { UsageLimitMessage } from "@/components/shared/usage-limit-message"; -import { ToastAction, useToast } from "@/components/ui"; -import { EntityInfo } from "@/components/ui/entities"; import { type ActionErrorResult, getActionErrorMessage, diff --git a/ui/components/providers/workflow/credentials-role-helper.tsx b/ui/components/providers/workflow/credentials-role-helper.tsx index 0ded59beee..12e0bd3502 100644 --- a/ui/components/providers/workflow/credentials-role-helper.tsx +++ b/ui/components/providers/workflow/credentials-role-helper.tsx @@ -2,7 +2,7 @@ import { IdIcon } from "@/components/icons"; import { Button } from "@/components/shadcn"; -import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; +import { CodeSnippet } from "@/components/shadcn/code-snippet/code-snippet"; import { IntegrationType } from "@/types/integrations"; interface CredentialsRoleHelperProps { @@ -92,7 +92,7 @@ export const CredentialsRoleHelper = ({
          - + External ID: } /> diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx index 227a2eada6..8bedaa2514 100644 --- a/ui/components/providers/workflow/forms/base-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx @@ -5,8 +5,8 @@ import { useEffect } from "react"; import { Control, UseFormSetValue } from "react-hook-form"; import { Button } from "@/components/shadcn"; +import { Form } from "@/components/shadcn/form"; import { Separator } from "@/components/shadcn/separator/separator"; -import { Form } from "@/components/ui/form"; import { useCredentialsForm } from "@/hooks/use-credentials-form"; import { getAWSCredentialsTemplateLinks } from "@/lib"; import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields"; diff --git a/ui/components/providers/workflow/forms/connect-account-form.tsx b/ui/components/providers/workflow/forms/connect-account-form.tsx index ddc5038160..7621fbe3e1 100644 --- a/ui/components/providers/workflow/forms/connect-account-form.tsx +++ b/ui/components/providers/workflow/forms/connect-account-form.tsx @@ -12,8 +12,8 @@ import { AwsMethodSelector } from "@/components/providers/organizations/aws-meth import { WizardInputField } from "@/components/providers/workflow/forms/fields"; import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs"; import { Button } from "@/components/shadcn"; -import { useToast } from "@/components/ui"; -import { Form } from "@/components/ui/form"; +import { useToast } from "@/components/shadcn"; +import { Form } from "@/components/shadcn/form"; import { addProviderFormSchema, ApiError, ProviderType } from "@/types"; import { RadioGroupProvider } from "../../radio-group-provider"; diff --git a/ui/components/providers/workflow/forms/fields/wizard-input-field.tsx b/ui/components/providers/workflow/forms/fields/wizard-input-field.tsx index b769bd1461..bcfd945807 100644 --- a/ui/components/providers/workflow/forms/fields/wizard-input-field.tsx +++ b/ui/components/providers/workflow/forms/fields/wizard-input-field.tsx @@ -4,8 +4,8 @@ import { Icon } from "@iconify/react"; import { InputHTMLAttributes, useState } from "react"; import { Control, FieldPath, FieldValues } from "react-hook-form"; +import { FormControl, FormField, FormMessage } from "@/components/shadcn/form"; import { Input } from "@/components/shadcn/input/input"; -import { FormControl, FormField, FormMessage } from "@/components/ui/form"; import { cn } from "@/lib/utils"; interface WizardInputFieldProps { @@ -142,7 +142,7 @@ export const WizardInputField = ({ ), @@ -74,7 +74,7 @@ vi.mock("@/components/shadcn/modal", () => ({ ) : null, })); -vi.mock("@/components/ui/entities", () => ({ +vi.mock("@/components/shadcn/entities", () => ({ EntityInfo: ({ entityAlias, entityId, diff --git a/ui/components/scans/launch-scan-modal.tsx b/ui/components/scans/launch-scan-modal.tsx index 5ae80ebf6b..b9c469ce6f 100644 --- a/ui/components/scans/launch-scan-modal.tsx +++ b/ui/components/scans/launch-scan-modal.tsx @@ -12,15 +12,15 @@ import { scanOnDemand } from "@/actions/scans"; import { getSchedule } from "@/actions/schedules"; import { AccountsSelector } from "@/app/(prowler)/_overview/_components/accounts-selector"; import { Field, FieldError, FieldLabel, Input } from "@/components/shadcn"; +import { FormButtons } from "@/components/shadcn/form"; import { Modal } from "@/components/shadcn/modal"; import { RadioGroup, RadioGroupItem, } from "@/components/shadcn/radio-group/radio-group"; +import { toast, ToastAction } from "@/components/shadcn/toast"; import { CloudFeatureBadgeLink } from "@/components/shared/cloud-feature-badge"; import { UsageLimitMessage } from "@/components/shared/usage-limit-message"; -import { FormButtons } from "@/components/ui/form"; -import { toast, ToastAction } from "@/components/ui/toast"; import { getActionErrorMessage, hasActionError } from "@/lib/action-errors"; import { buildScheduleAttributesFromProvider, diff --git a/ui/components/scans/scan-error-details-modal.test.tsx b/ui/components/scans/scan-error-details-modal.test.tsx index 1474e7eea1..f7993fc632 100644 --- a/ui/components/scans/scan-error-details-modal.test.tsx +++ b/ui/components/scans/scan-error-details-modal.test.tsx @@ -6,7 +6,7 @@ import { type ScanErrorDetailsState, } from "./scan-error-details-modal"; -vi.mock("@/components/ui/code-snippet/code-snippet", () => ({ +vi.mock("@/components/shadcn/code-snippet/code-snippet", () => ({ CodeSnippet: ({ value, formatter, diff --git a/ui/components/scans/scan-error-details-view.test.tsx b/ui/components/scans/scan-error-details-view.test.tsx index 145f84d529..30fe5ed83e 100644 --- a/ui/components/scans/scan-error-details-view.test.tsx +++ b/ui/components/scans/scan-error-details-view.test.tsx @@ -5,7 +5,7 @@ import type { ScanErrorDetails } from "@/actions/task/task.adapter"; import { ScanErrorDetailsView } from "./scan-error-details-view"; -vi.mock("@/components/ui/code-snippet/code-snippet", () => ({ +vi.mock("@/components/shadcn/code-snippet/code-snippet", () => ({ CodeSnippet: ({ value, formatter, diff --git a/ui/components/scans/scan-error-details-view.tsx b/ui/components/scans/scan-error-details-view.tsx index 8670e20ba4..b6e9b641e9 100644 --- a/ui/components/scans/scan-error-details-view.tsx +++ b/ui/components/scans/scan-error-details-view.tsx @@ -2,7 +2,7 @@ import type { ScanErrorDetails } from "@/actions/task/task.adapter"; import { Field, FieldLabel, LabeledField } from "@/components/shadcn"; -import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet"; +import { CodeSnippet } from "@/components/shadcn/code-snippet/code-snippet"; interface ScanErrorDetailsViewProps { details: ScanErrorDetails; diff --git a/ui/components/scans/scans-filter-bar.test.tsx b/ui/components/scans/scans-filter-bar.test.tsx index 50aea99fe8..366b91c9b0 100644 --- a/ui/components/scans/scans-filter-bar.test.tsx +++ b/ui/components/scans/scans-filter-bar.test.tsx @@ -13,7 +13,8 @@ vi.mock("@/components/filters/provider-group-selector", () => ({ ProviderGroupSelector: () =>
          Provider group selector
          , })); -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), Select: ({ children }: { children: React.ReactNode }) => (
          {children}
          ), diff --git a/ui/components/scans/schedule/edit-scan-schedule-modal.test.tsx b/ui/components/scans/schedule/edit-scan-schedule-modal.test.tsx index 27f5c302de..e8e64ce77a 100644 --- a/ui/components/scans/schedule/edit-scan-schedule-modal.test.tsx +++ b/ui/components/scans/schedule/edit-scan-schedule-modal.test.tsx @@ -26,7 +26,7 @@ vi.mock("@/actions/schedules", () => ({ updateSchedulesBulk: updateSchedulesBulkMock, })); -vi.mock("@/components/ui/toast", () => ({ +vi.mock("@/components/shadcn/toast", () => ({ toast: toastMock, })); @@ -40,7 +40,7 @@ vi.mock("@/components/icons/providers-badge/provider-type-icon", () => ({ ), })); -vi.mock("@/components/ui/entities", () => ({ +vi.mock("@/components/shadcn/entities", () => ({ EntityInfo: ({ badge, cloudProvider, diff --git a/ui/components/scans/schedule/edit-scan-schedule-modal.tsx b/ui/components/scans/schedule/edit-scan-schedule-modal.tsx index 73fd781411..f8caa12c1f 100644 --- a/ui/components/scans/schedule/edit-scan-schedule-modal.tsx +++ b/ui/components/scans/schedule/edit-scan-schedule-modal.tsx @@ -16,10 +16,10 @@ import { type ProviderTypeIconStackItem, } from "@/components/icons/providers-badge/provider-type-icon"; import { Button, FieldError } from "@/components/shadcn"; +import { EntityInfo } from "@/components/shadcn/entities"; +import { FormButtons } from "@/components/shadcn/form"; import { Modal } from "@/components/shadcn/modal"; -import { EntityInfo } from "@/components/ui/entities"; -import { FormButtons } from "@/components/ui/form"; -import { toast } from "@/components/ui/toast"; +import { toast } from "@/components/shadcn/toast"; import { getActionErrorMessage, hasActionError } from "@/lib/action-errors"; import { runWithConcurrencyLimit } from "@/lib/concurrency"; import { diff --git a/ui/components/scans/table/__tests__/scan-jobs-table.test.tsx b/ui/components/scans/table/__tests__/scan-jobs-table.test.tsx index 8251bdddc4..53aefda632 100644 --- a/ui/components/scans/table/__tests__/scan-jobs-table.test.tsx +++ b/ui/components/scans/table/__tests__/scan-jobs-table.test.tsx @@ -7,7 +7,7 @@ import { ScanJobsTable } from "../scan-jobs-table"; // Mock DataTable to a minimal table that applies getRowAttributes per row, so we can // assert the view-first-scan "in-progress" anchor lands on the right row/tab. -vi.mock("@/components/ui/table", () => ({ +vi.mock("@/components/shadcn/table", () => ({ DataTable: ({ data, getRowAttributes, diff --git a/ui/components/scans/table/cells/account-cell.tsx b/ui/components/scans/table/cells/account-cell.tsx index ecc7bcc987..104e94610e 100644 --- a/ui/components/scans/table/cells/account-cell.tsx +++ b/ui/components/scans/table/cells/account-cell.tsx @@ -1,6 +1,6 @@ "use client"; -import { EntityInfo } from "@/components/ui/entities"; +import { EntityInfo } from "@/components/shadcn/entities"; import type { ProviderType, ScanProps } from "@/types"; export function AccountCell({ scan }: { scan: ScanProps }) { diff --git a/ui/components/scans/table/cells/scan-info-cell.tsx b/ui/components/scans/table/cells/scan-info-cell.tsx index 7731a2fae1..226732d8fa 100644 --- a/ui/components/scans/table/cells/scan-info-cell.tsx +++ b/ui/components/scans/table/cells/scan-info-cell.tsx @@ -7,7 +7,7 @@ import { TooltipContent, TooltipTrigger, } from "@/components/shadcn"; -import { EntityInfo } from "@/components/ui/entities"; +import { EntityInfo } from "@/components/shadcn/entities"; import type { ScanProps } from "@/types"; export function ScanInfoCell({ scan }: { scan: ScanProps }) { diff --git a/ui/components/scans/table/scan-jobs-columns.test.tsx b/ui/components/scans/table/scan-jobs-columns.test.tsx index 4f8891666c..3a30d74800 100644 --- a/ui/components/scans/table/scan-jobs-columns.test.tsx +++ b/ui/components/scans/table/scan-jobs-columns.test.tsx @@ -5,7 +5,8 @@ import { describe, expect, it, vi } from "vitest"; import type { ScanProps } from "@/types"; -vi.mock("@/components/shadcn", () => ({ +vi.mock("@/components/shadcn", async (importOriginal) => ({ + ...(await importOriginal>()), Badge: ({ children }: { children: ReactNode }) => {children}, Progress: () =>
          , StackedCell: ({ @@ -22,7 +23,7 @@ vi.mock("@/components/shadcn", () => ({ ), })); -vi.mock("@/components/ui/entities", () => ({ +vi.mock("@/components/shadcn/entities", () => ({ DateWithTime: () =>