diff --git a/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx
deleted file mode 100644
index 56fc10c03b..0000000000
--- a/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { getProvider } from "@/actions/providers/providers";
-import {
- AddViaCredentialsForm,
- AddViaRoleForm,
-} from "@/components/providers/workflow/forms";
-import { SelectViaAlibabaCloud } from "@/components/providers/workflow/forms/select-credentials-type/alibabacloud";
-import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws";
-import {
- AddViaServiceAccountForm,
- SelectViaGCP,
-} from "@/components/providers/workflow/forms/select-credentials-type/gcp";
-import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github";
-import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365";
-import { getProviderFormType } from "@/lib/provider-helpers";
-import { ProviderType } from "@/types/providers";
-
-interface Props {
- searchParams: Promise<{ type: ProviderType; id: string; via?: string }>;
-}
-
-export default async function AddCredentialsPage({ searchParams }: Props) {
- const resolvedSearchParams = await searchParams;
- const { type: providerType, via, id: providerId } = resolvedSearchParams;
- const formType = getProviderFormType(providerType, via);
-
- // Fetch provider data to get the UID (needed for OCI)
- let providerUid: string | undefined;
- if (providerId) {
- const formData = new FormData();
- formData.append("id", providerId);
- const providerResponse = await getProvider(formData);
- if (providerResponse?.data?.attributes?.uid) {
- providerUid = providerResponse.data.attributes.uid;
- }
- }
-
- switch (formType) {
- case "selector":
- if (providerType === "aws") return ;
- if (providerType === "gcp") return ;
- if (providerType === "github")
- return ;
- if (providerType === "m365") return ;
- if (providerType === "alibabacloud")
- return ;
- return null;
-
- case "credentials":
- return (
-
- );
-
- case "role":
- return (
-
- );
-
- case "service-account":
- return (
-
- );
-
- default:
- return null;
- }
-}
diff --git a/ui/app/(prowler)/providers/(set-up-provider)/connect-account/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/connect-account/page.tsx
deleted file mode 100644
index eead6c2561..0000000000
--- a/ui/app/(prowler)/providers/(set-up-provider)/connect-account/page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from "react";
-
-import { ConnectAccountForm } from "@/components/providers/workflow/forms";
-
-export default function ConnectAccountPage() {
- return ;
-}
diff --git a/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx b/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx
deleted file mode 100644
index 78110077c3..0000000000
--- a/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import "@/styles/globals.css";
-
-import React from "react";
-
-import { WorkflowAddProvider } from "@/components/providers/workflow";
-import { NavigationHeader } from "@/components/ui";
-
-interface ProviderLayoutProps {
- children: React.ReactNode;
-}
-
-export default function ProviderLayout({ children }: ProviderLayoutProps) {
- return (
- <>
-
-
-
- >
- );
-}
diff --git a/ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx
deleted file mode 100644
index 161025cf02..0000000000
--- a/ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { redirect } from "next/navigation";
-import React, { Suspense } from "react";
-
-import { getProvider } from "@/actions/providers";
-import { SkeletonProviderWorkflow } from "@/components/providers/workflow";
-import { TestConnectionForm } from "@/components/providers/workflow/forms";
-
-interface Props {
- searchParams: Promise<{ type: string; id: string; updated: string }>;
-}
-
-export default async function TestConnectionPage({ searchParams }: Props) {
- const resolvedSearchParams = await searchParams;
- const providerId = resolvedSearchParams.id;
-
- if (!providerId) {
- redirect("/providers/connect-account");
- }
-
- return (
- }>
-
-
- );
-}
-
-async function SSRTestConnection({
- searchParams,
-}: {
- searchParams: { type: string; id: string; updated: string };
-}) {
- const formData = new FormData();
- formData.append("id", searchParams.id);
-
- const providerData = await getProvider(formData);
- if (providerData.errors) {
- redirect("/providers/connect-account");
- }
-
- return (
-
- );
-}
diff --git a/ui/app/(prowler)/providers/(set-up-provider)/update-credentials/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/update-credentials/page.tsx
deleted file mode 100644
index cd6af05043..0000000000
--- a/ui/app/(prowler)/providers/(set-up-provider)/update-credentials/page.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { redirect } from "next/navigation";
-import React from "react";
-
-import { getProvider } from "@/actions/providers/providers";
-import { CredentialsUpdateInfo } from "@/components/providers";
-import {
- UpdateViaCredentialsForm,
- UpdateViaRoleForm,
-} from "@/components/providers/workflow/forms";
-import { UpdateViaServiceAccountForm } from "@/components/providers/workflow/forms/update-via-service-account-key-form";
-import { getProviderFormType } from "@/lib/provider-helpers";
-import { ProviderType } from "@/types/providers";
-
-interface Props {
- searchParams: Promise<{
- type: ProviderType;
- id: string;
- via?: string;
- secretId?: string;
- }>;
-}
-
-export default async function UpdateCredentialsPage({ searchParams }: Props) {
- const resolvedSearchParams = await searchParams;
- const { type: providerType, via, id: providerId } = resolvedSearchParams;
-
- if (!providerId) {
- redirect("/providers");
- }
-
- const formType = getProviderFormType(providerType, via);
-
- const formData = new FormData();
- formData.append("id", providerId);
- const providerResponse = await getProvider(formData);
-
- if (providerResponse?.errors) {
- redirect("/providers");
- }
-
- const providerUid = providerResponse?.data?.attributes?.uid;
-
- switch (formType) {
- case "selector":
- return (
-
- );
-
- case "credentials":
- return (
-
- );
-
- case "role":
- return (
-
- );
-
- case "service-account":
- return (
-
- );
-
- default:
- return null;
- }
-}
diff --git a/ui/components/lighthouse/workflow/workflow-connect-llm.tsx b/ui/components/lighthouse/workflow/workflow-connect-llm.tsx
index d99ebd4811..455e244fca 100644
--- a/ui/components/lighthouse/workflow/workflow-connect-llm.tsx
+++ b/ui/components/lighthouse/workflow/workflow-connect-llm.tsx
@@ -5,7 +5,7 @@ import { Spacer } from "@heroui/spacer";
import { usePathname, useSearchParams } from "next/navigation";
import React from "react";
-import { VerticalSteps } from "@/components/providers/workflow/vertical-steps";
+import { cn } from "@/lib/utils";
import type { LighthouseProvider } from "@/types/lighthouse";
import { getProviderConfig } from "../llm-provider-registry";
@@ -75,12 +75,60 @@ export const WorkflowConnectLLM = () => {
value={currentStep + 1}
valueLabel={`${currentStep + 1} of ${steps.length}`}
/>
-
+
);
diff --git a/ui/components/providers/organizations/org-account-selection.tsx b/ui/components/providers/organizations/org-account-selection.tsx
index d9a2b9aa90..51d229b1dd 100644
--- a/ui/components/providers/organizations/org-account-selection.tsx
+++ b/ui/components/providers/organizations/org-account-selection.tsx
@@ -1,12 +1,16 @@
"use client";
-import { ChevronLeft, ChevronRight } from "lucide-react";
+import { useEffect } from "react";
import {
buildAccountLookup,
buildOrgTreeData,
} from "@/actions/organizations/organizations.adapter";
-import { Badge, Button } from "@/components/shadcn";
+import {
+ WIZARD_FOOTER_ACTION_TYPE,
+ WizardFooterConfig,
+} from "@/components/providers/wizard/steps/footer-controls";
+import { Badge } from "@/components/shadcn";
import { TreeView } from "@/components/shadcn/tree-view";
import { useOrgSetupStore } from "@/store/organizations/store";
@@ -15,11 +19,13 @@ import { OrgAccountTreeItem, TREE_ITEM_MODE } from "./org-account-tree-item";
interface OrgAccountSelectionProps {
onBack: () => void;
onNext: () => void;
+ onFooterChange: (config: WizardFooterConfig) => void;
}
export function OrgAccountSelection({
onBack,
onNext,
+ onFooterChange,
}: OrgAccountSelectionProps) {
const {
organizationName,
@@ -31,6 +37,21 @@ export function OrgAccountSelection({
setAccountAlias,
} = useOrgSetupStore();
+ const selectedCount = selectedAccountIds.length;
+
+ useEffect(() => {
+ onFooterChange({
+ showBack: true,
+ backLabel: "Back",
+ onBack,
+ showAction: true,
+ actionLabel: "Next",
+ actionDisabled: selectedCount === 0,
+ actionType: WIZARD_FOOTER_ACTION_TYPE.BUTTON,
+ onAction: onNext,
+ });
+ }, [onBack, onFooterChange, onNext, selectedCount]);
+
if (!discoveryResult) {
return (
@@ -43,7 +64,6 @@ export function OrgAccountSelection({
const accountLookup = buildAccountLookup(discoveryResult);
const totalAccounts = discoveryResult.accounts.length;
- const selectedCount = selectedAccountIds.length;
return (
@@ -82,18 +102,6 @@ export function OrgAccountSelection({
)}
/>
-
- {/* Actions */}
-
-
-
-
);
}
diff --git a/ui/components/providers/organizations/org-connection-test.tsx b/ui/components/providers/organizations/org-connection-test.tsx
index 78c34fbeeb..d7febc0be5 100644
--- a/ui/components/providers/organizations/org-connection-test.tsx
+++ b/ui/components/providers/organizations/org-connection-test.tsx
@@ -1,6 +1,6 @@
"use client";
-import { AlertTriangle, Loader2, RefreshCw } from "lucide-react";
+import { AlertTriangle, Loader2 } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { applyDiscovery } from "@/actions/organizations/organizations";
@@ -10,7 +10,11 @@ import {
getOuIdsForSelectedAccounts,
} from "@/actions/organizations/organizations.adapter";
import { checkConnectionProvider } from "@/actions/providers/providers";
-import { Badge, Button } from "@/components/shadcn";
+import {
+ WIZARD_FOOTER_ACTION_TYPE,
+ WizardFooterConfig,
+} from "@/components/providers/wizard/steps/footer-controls";
+import { Badge } from "@/components/shadcn";
import { TreeView } from "@/components/shadcn/tree-view";
import { checkTaskStatus } from "@/lib";
import { useOrgSetupStore } from "@/store/organizations/store";
@@ -22,12 +26,14 @@ interface OrgConnectionTestProps {
onBack: () => void;
onNext: () => void;
onSkip: () => void;
+ onFooterChange: (config: WizardFooterConfig) => void;
}
export function OrgConnectionTest({
onBack,
onNext,
onSkip,
+ onFooterChange,
}: OrgConnectionTestProps) {
const {
organizationId,
@@ -51,6 +57,7 @@ export function OrgConnectionTest({
>(new Map());
const hasApplied = useRef(false);
+ const retryFailedActionRef = useRef<() => void>(() => {});
// On mount: apply discovery, then test connections
useEffect(() => {
@@ -168,22 +175,36 @@ export function OrgConnectionTest({
}
};
- const handleRetryFailed = () => {
+ retryFailedActionRef.current = () => {
const failedProviderIds = createdProviderIds.filter(
(id) => connectionResults[id] === CONNECTION_TEST_STATUS.ERROR,
);
testAllConnections(failedProviderIds);
};
+ const hasErrors = Object.values(connectionResults).some(
+ (s) => s === CONNECTION_TEST_STATUS.ERROR,
+ );
+
+ useEffect(() => {
+ onFooterChange({
+ showBack: true,
+ backLabel: "Back",
+ backDisabled: isApplying || isTesting,
+ onBack,
+ showAction: hasErrors || isTesting,
+ actionLabel: isTesting ? "Testing..." : "Test connections",
+ actionDisabled: isApplying || isTesting || !hasErrors,
+ actionType: WIZARD_FOOTER_ACTION_TYPE.BUTTON,
+ onAction: hasErrors ? () => retryFailedActionRef.current() : undefined,
+ });
+ }, [hasErrors, isApplying, isTesting, onBack, onFooterChange]);
+
if (!discoveryResult) return null;
const treeData = buildOrgTreeData(discoveryResult);
const accountLookup = buildAccountLookup(discoveryResult);
- const hasErrors = Object.values(connectionResults).some(
- (s) => s === CONNECTION_TEST_STATUS.ERROR,
- );
-
return (
{/* Header */}
@@ -244,12 +265,8 @@ export function OrgConnectionTest({
)}
- {/* Actions */}
-
-
-
+ {/* Optional skip */}
+
-
- {hasErrors && !isTesting && (
-
- )}
-
- {isTesting && (
-
- )}
diff --git a/ui/components/providers/organizations/org-launch-scan.tsx b/ui/components/providers/organizations/org-launch-scan.tsx
index dce48d94e4..6ffaac0468 100644
--- a/ui/components/providers/organizations/org-launch-scan.tsx
+++ b/ui/components/providers/organizations/org-launch-scan.tsx
@@ -2,18 +2,28 @@
import { CheckCircle2, Loader2 } from "lucide-react";
import { useRouter } from "next/navigation";
-import { useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { scheduleDaily } from "@/actions/scans/scans";
-import { Badge, Button } from "@/components/shadcn";
+import {
+ WIZARD_FOOTER_ACTION_TYPE,
+ WizardFooterConfig,
+} from "@/components/providers/wizard/steps/footer-controls";
+import { Badge } from "@/components/shadcn";
import { useToast } from "@/components/ui";
import { useOrgSetupStore } from "@/store/organizations/store";
interface OrgLaunchScanProps {
onClose: () => void;
+ onBack: () => void;
+ onFooterChange: (config: WizardFooterConfig) => void;
}
-export function OrgLaunchScan({ onClose }: OrgLaunchScanProps) {
+export function OrgLaunchScan({
+ onClose,
+ onBack,
+ onFooterChange,
+}: OrgLaunchScanProps) {
const router = useRouter();
const { toast } = useToast();
const {
@@ -24,12 +34,7 @@ export function OrgLaunchScan({ onClose }: OrgLaunchScanProps) {
} = useOrgSetupStore();
const [isLaunching, setIsLaunching] = useState(false);
-
- const handleDone = () => {
- reset();
- onClose();
- router.push("/providers");
- };
+ const launchActionRef = useRef<() => void>(() => {});
const handleLaunchScan = async () => {
setIsLaunching(true);
@@ -57,6 +62,26 @@ export function OrgLaunchScan({ onClose }: OrgLaunchScanProps) {
});
};
+ launchActionRef.current = () => {
+ void handleLaunchScan();
+ };
+
+ useEffect(() => {
+ onFooterChange({
+ showBack: true,
+ backLabel: "Back",
+ backDisabled: isLaunching,
+ onBack,
+ showAction: true,
+ actionLabel: isLaunching ? "Launching..." : "Launch scan",
+ actionDisabled: isLaunching,
+ actionType: WIZARD_FOOTER_ACTION_TYPE.BUTTON,
+ onAction: () => {
+ launchActionRef.current();
+ },
+ });
+ }, [isLaunching, onBack, onFooterChange]);
+
return (
{/* Org info */}
@@ -87,16 +112,12 @@ export function OrgLaunchScan({ onClose }: OrgLaunchScanProps) {
- {/* Actions */}
-
-
-
-
+ {isLaunching && (
+
+
+ Launching scans...
+
+ )}
);
}
diff --git a/ui/components/providers/organizations/org-setup-form.tsx b/ui/components/providers/organizations/org-setup-form.tsx
index 32efdd6ad7..ea4a52910a 100644
--- a/ui/components/providers/organizations/org-setup-form.tsx
+++ b/ui/components/providers/organizations/org-setup-form.tsx
@@ -2,7 +2,7 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Loader2 } from "lucide-react";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -11,7 +11,10 @@ import {
createOrganizationSecret,
triggerDiscovery,
} from "@/actions/organizations/organizations";
-import { Button } from "@/components/shadcn";
+import {
+ WIZARD_FOOTER_ACTION_TYPE,
+ WizardFooterConfig,
+} from "@/components/providers/wizard/steps/footer-controls";
import { Input } from "@/components/shadcn/input/input";
import { useOrgSetupStore } from "@/store/organizations/store";
@@ -39,19 +42,27 @@ type OrgSetupFormData = z.infer;
interface OrgSetupFormProps {
onBack: () => void;
onNext: () => void;
+ onFooterChange: (config: WizardFooterConfig) => void;
}
-export function OrgSetupForm({ onBack, onNext }: OrgSetupFormProps) {
+export function OrgSetupForm({
+ onBack,
+ onNext,
+ onFooterChange,
+}: OrgSetupFormProps) {
const [apiError, setApiError] = useState(null);
const { setOrganization, setDiscovery } = useOrgSetupStore();
+ const formId = "org-wizard-setup-form";
const {
register,
handleSubmit,
- formState: { errors, isSubmitting },
+ formState: { errors, isSubmitting, isValid },
setError,
} = useForm({
resolver: zodResolver(orgSetupSchema),
+ mode: "onChange",
+ reValidateMode: "onChange",
defaultValues: {
organizationName: "",
awsOrgId: "",
@@ -60,6 +71,20 @@ export function OrgSetupForm({ onBack, onNext }: OrgSetupFormProps) {
},
});
+ useEffect(() => {
+ onFooterChange({
+ showBack: true,
+ backLabel: "Back",
+ backDisabled: isSubmitting,
+ onBack,
+ showAction: true,
+ actionLabel: "Next",
+ actionDisabled: isSubmitting || !isValid,
+ actionType: WIZARD_FOOTER_ACTION_TYPE.SUBMIT,
+ actionFormId: formId,
+ });
+ }, [formId, isSubmitting, isValid, onBack, onFooterChange]);
+
const onSubmit = async (data: OrgSetupFormData) => {
setApiError(null);
@@ -131,7 +156,11 @@ export function OrgSetupForm({ onBack, onNext }: OrgSetupFormProps) {
};
return (
-
);
}
diff --git a/ui/components/providers/organizations/org-wizard-modal.tsx b/ui/components/providers/organizations/org-wizard-modal.tsx
deleted file mode 100644
index cfb0544b0c..0000000000
--- a/ui/components/providers/organizations/org-wizard-modal.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-"use client";
-
-import { ExternalLink } from "lucide-react";
-import { useState } from "react";
-
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from "@/components/shadcn/dialog";
-import { useOrgSetupStore } from "@/store/organizations/store";
-import { ORG_WIZARD_STEP, OrgWizardStep } from "@/types/organizations";
-
-import { OrgAccountSelection } from "./org-account-selection";
-import { OrgConnectionTest } from "./org-connection-test";
-import { OrgDiscoveryLoader } from "./org-discovery-loader";
-import { OrgLaunchScan } from "./org-launch-scan";
-import { OrgSetupForm } from "./org-setup-form";
-import { OrgWizardStepper } from "./org-wizard-stepper";
-
-const VALIDATE_PHASE = {
- DISCOVERY: "discovery",
- SELECTION: "selection",
- TESTING: "testing",
-} as const;
-
-type ValidatePhase = (typeof VALIDATE_PHASE)[keyof typeof VALIDATE_PHASE];
-
-interface OrgWizardModalProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function OrgWizardModal({ open, onOpenChange }: OrgWizardModalProps) {
- const [currentStep, setCurrentStep] = useState(
- ORG_WIZARD_STEP.SETUP,
- );
- const [validatePhase, setValidatePhase] = useState(
- VALIDATE_PHASE.DISCOVERY,
- );
-
- const { connectionResults, reset } = useOrgSetupStore();
-
- const hasConnectionErrors = Object.values(connectionResults).some(
- (s) => s === "error",
- );
-
- const handleClose = () => {
- reset();
- setCurrentStep(ORG_WIZARD_STEP.SETUP);
- setValidatePhase(VALIDATE_PHASE.DISCOVERY);
- onOpenChange(false);
- };
-
- const handleSetupBack = () => {
- handleClose();
- };
-
- const handleSetupNext = () => {
- setCurrentStep(ORG_WIZARD_STEP.VALIDATE);
- setValidatePhase(VALIDATE_PHASE.DISCOVERY);
- };
-
- const handleDiscoveryComplete = () => {
- setValidatePhase(VALIDATE_PHASE.SELECTION);
- };
-
- const handleSelectionBack = () => {
- // Go back to setup form
- setCurrentStep(ORG_WIZARD_STEP.SETUP);
- };
-
- const handleSelectionNext = () => {
- setValidatePhase(VALIDATE_PHASE.TESTING);
- };
-
- const handleTestingBack = () => {
- setValidatePhase(VALIDATE_PHASE.SELECTION);
- };
-
- const handleTestingNext = () => {
- setCurrentStep(ORG_WIZARD_STEP.LAUNCH);
- };
-
- const handleSkipValidation = () => {
- setCurrentStep(ORG_WIZARD_STEP.LAUNCH);
- };
-
- return (
-
- );
-}
diff --git a/ui/components/providers/organizations/org-wizard-stepper.tsx b/ui/components/providers/organizations/org-wizard-stepper.tsx
deleted file mode 100644
index a1007d31b8..0000000000
--- a/ui/components/providers/organizations/org-wizard-stepper.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-"use client";
-
-import {
- AlertCircle,
- CircleCheckBig,
- FolderGit2,
- KeyRound,
- Rocket,
-} from "lucide-react";
-
-import { ProwlerShort } from "@/components/icons/prowler/ProwlerIcons";
-import { cn } from "@/lib/utils";
-import { IconComponent, IconSvgProps } from "@/types/components";
-import { OrgWizardStep } from "@/types/organizations";
-
-interface OrgWizardStepperProps {
- currentStep: OrgWizardStep;
- hasConnectionErrors: boolean;
-}
-
-interface StepConfig {
- label: string;
- description: string;
- icon: IconComponent;
-}
-
-const STEPS: StepConfig[] = [
- {
- label: "Link a Cloud Provider",
- description: "Enter the provider details you would like to add in Prowler.",
- icon: FolderGit2,
- },
- {
- label: "Authenticate Credentials",
- description:
- "Authorize a secure connection between Prowler and your provider.",
- icon: KeyRound,
- },
- {
- label: "Validate Connection",
- description:
- "Review provider resources and test the connection to Prowler.",
- icon: Rocket,
- },
- {
- label: "Launch Scan",
- description: "Scan newly connected resources.",
- icon: ProwlerShort,
- },
-];
-
-/**
- * Maps modal internal step (0=Setup, 1=Validate, 2=Launch) to the
- * visual stepper index (0-3). Step 0 in the stepper is always complete.
- */
-function getVisualStepIndex(modalStep: OrgWizardStep): number {
- return modalStep + 1;
-}
-
-export function OrgWizardStepper({
- currentStep,
- hasConnectionErrors,
-}: OrgWizardStepperProps) {
- const activeVisualStep = getVisualStepIndex(currentStep);
-
- return (
-
- );
-}
-
-interface StepCircleProps {
- isComplete: boolean;
- isActive: boolean;
- isError: boolean;
- icon: IconComponent;
-}
-
-function StepCircle({
- isComplete,
- isActive,
- isError,
- icon: Icon,
-}: StepCircleProps) {
- if (isError) {
- return (
-
- );
- }
-
- if (isComplete) {
- return (
-
-
-
- );
- }
-
- if (isActive) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
- );
-}
-
-function StepConnector({ isComplete }: { isComplete: boolean }) {
- if (isComplete) {
- return ;
- }
-
- return (
-
- );
-}
-
-/**
- * Renders either a Lucide icon (className-based) or a custom SVG icon
- * (size-prop-based like ProwlerShort) with consistent sizing.
- */
-function StepIcon({
- icon: Icon,
- className,
-}: {
- icon: IconComponent;
- className: string;
-}) {
- if (isCustomSvgIcon(Icon)) {
- return ;
- }
- return ;
-}
-
-/**
- * Type guard: Lucide icons have a `displayName` property set by lucide-react.
- * Custom SVG icons (React.FC) accept a `size` number prop.
- */
-function isCustomSvgIcon(icon: IconComponent): icon is React.FC {
- return !("displayName" in icon && typeof icon.displayName === "string");
-}
diff --git a/ui/components/providers/workflow/index.ts b/ui/components/providers/workflow/index.ts
index fa64a01884..b2bc271617 100644
--- a/ui/components/providers/workflow/index.ts
+++ b/ui/components/providers/workflow/index.ts
@@ -1,4 +1,3 @@
export * from "./credentials-role-helper";
export * from "./provider-title-docs";
export * from "./skeleton-provider-workflow";
-export * from "./workflow-add-provider";
diff --git a/ui/components/providers/workflow/vertical-steps.tsx b/ui/components/providers/workflow/vertical-steps.tsx
deleted file mode 100644
index 2971d3080c..0000000000
--- a/ui/components/providers/workflow/vertical-steps.tsx
+++ /dev/null
@@ -1,294 +0,0 @@
-"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";
-
-export type VerticalStepProps = {
- className?: string;
- description?: React.ReactNode;
- title?: React.ReactNode;
-};
-
-export interface VerticalStepsProps
- extends React.HTMLAttributes {
- /**
- * An array of steps.
- *
- * @default []
- */
- steps?: VerticalStepProps[];
- /**
- * The color of the steps.
- *
- * @default "primary"
- */
- color?:
- | "primary"
- | "secondary"
- | "success"
- | "warning"
- | "danger"
- | "default";
- /**
- * The current step index.
- */
- currentStep?: number;
- /**
- * The default step index.
- *
- * @default 0
- */
- defaultStep?: number;
- /**
- * Whether to hide the progress bars.
- *
- * @default false
- */
- hideProgressBars?: boolean;
- /**
- * The custom class for the steps wrapper.
- */
- className?: string;
- /**
- * The custom class for the step.
- */
- stepClassName?: string;
- /**
- * Callback function when the step index changes.
- */
- onStepChange?: (stepIndex: number) => void;
-}
-
-function CheckIcon(props: ComponentProps<"svg">) {
- return (
-
- );
-}
-
-export const VerticalSteps = React.forwardRef<
- HTMLButtonElement,
- VerticalStepsProps
->(
- (
- {
- color = "primary",
- steps = [],
- defaultStep = 0,
- onStepChange,
- currentStep: currentStepProp,
- hideProgressBars = false,
- stepClassName,
- className,
- ...props
- },
- ref,
- ) => {
- const [currentStep, setCurrentStep] = useControlledState(
- currentStepProp,
- defaultStep,
- onStepChange,
- );
-
- const colors = React.useMemo(() => {
- let userColor;
- let fgColor;
-
- const colorsVars = [
- "[--active-fg-color:var(--step-fg-color)]",
- "[--active-border-color:var(--step-color)]",
- "[--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))]",
- ];
-
- switch (color) {
- case "primary":
- userColor = "[--step-color:hsl(var(--heroui-primary))]";
- fgColor = "[--step-fg-color:hsl(var(--heroui-primary-foreground))]";
- break;
- case "secondary":
- userColor = "[--step-color:hsl(var(--heroui-secondary))]";
- fgColor = "[--step-fg-color:hsl(var(--heroui-secondary-foreground))]";
- break;
- case "success":
- userColor = "[--step-color:hsl(var(--heroui-success))]";
- fgColor = "[--step-fg-color:hsl(var(--heroui-success-foreground))]";
- break;
- case "warning":
- userColor = "[--step-color:hsl(var(--heroui-warning))]";
- fgColor = "[--step-fg-color:hsl(var(--heroui-warning-foreground))]";
- break;
- case "danger":
- userColor = "[--step-color:hsl(var(--heroui-error))]";
- fgColor = "[--step-fg-color:hsl(var(--heroui-error-foreground))]";
- break;
- case "default":
- userColor = "[--step-color:hsl(var(--heroui-default))]";
- fgColor = "[--step-fg-color:hsl(var(--heroui-default-foreground))]";
- break;
- default:
- userColor = "[--step-color:hsl(var(--heroui-primary))]";
- fgColor = "[--step-fg-color:hsl(var(--heroui-primary-foreground))]";
- break;
- }
-
- if (!className?.includes("--step-fg-color")) colorsVars.unshift(fgColor);
- if (!className?.includes("--step-color")) colorsVars.unshift(userColor);
- if (!className?.includes("--inactive-bar-color"))
- colorsVars.push(
- "[--inactive-bar-color:hsl(var(--heroui-default-300))]",
- );
-
- return colorsVars;
- }, [color, className]);
-
- return (
-
- );
- },
-);
-
-VerticalSteps.displayName = "VerticalSteps";
diff --git a/ui/components/providers/workflow/workflow-add-provider.tsx b/ui/components/providers/workflow/workflow-add-provider.tsx
deleted file mode 100644
index e0bb8149c9..0000000000
--- a/ui/components/providers/workflow/workflow-add-provider.tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-"use client";
-
-import {
- CircleCheckBig,
- FolderGit2,
- KeyRound,
- Rocket,
-} from "lucide-react";
-import { usePathname } from "next/navigation";
-
-import { ProwlerShort } from "@/components/icons/prowler/ProwlerIcons";
-import { cn } from "@/lib/utils";
-import { IconComponent, IconSvgProps } from "@/types/components";
-
-interface StepConfig {
- label: string;
- description: string;
- icon: IconComponent;
-}
-
-const STEPS: StepConfig[] = [
- {
- label: "Link a Cloud Provider",
- description:
- "Enter the provider details you would like to add in Prowler.",
- icon: FolderGit2,
- },
- {
- label: "Authenticate Credentials",
- description:
- "Authorize a secure connection between Prowler and your provider.",
- icon: KeyRound,
- },
- {
- label: "Validate Connection",
- description:
- "Review provider resources and test the connection to Prowler.",
- icon: Rocket,
- },
- {
- label: "Launch Scan",
- description: "Scan newly connected resources.",
- icon: ProwlerShort,
- },
-];
-
-const ROUTE_TO_STEP: Record = {
- "/providers/connect-account": 0,
- "/providers/add-credentials": 1,
- "/providers/test-connection": 2,
- "/providers/update-credentials": 1,
-};
-
-export const WorkflowAddProvider = () => {
- const pathname = usePathname();
- const currentStep = ROUTE_TO_STEP[pathname] ?? 0;
-
- return (
-
- );
-};
-
-interface StepCircleProps {
- isComplete: boolean;
- isActive: boolean;
- icon: IconComponent;
-}
-
-function StepCircle({ isComplete, isActive, icon: Icon }: StepCircleProps) {
- if (isComplete) {
- return (
-
-
-
- );
- }
-
- if (isActive) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
- );
-}
-
-function StepConnector({ isComplete }: { isComplete: boolean }) {
- if (isComplete) {
- return ;
- }
-
- return (
-
- );
-}
-
-/**
- * Renders either a Lucide icon (className-based) or a custom SVG icon
- * (size-prop-based like ProwlerShort) with consistent sizing.
- */
-function StepIcon({
- icon: Icon,
- className,
-}: {
- icon: IconComponent;
- className: string;
-}) {
- if (isCustomSvgIcon(Icon)) {
- return ;
- }
- return ;
-}
-
-/**
- * Type guard: Lucide icons have a `displayName` property set by lucide-react.
- * Custom SVG icons (React.FC) accept a `size` number prop.
- */
-function isCustomSvgIcon(icon: IconComponent): icon is React.FC {
- return !("displayName" in icon && typeof icon.displayName === "string");
-}