From d9a142a445e004a80103db6a6fcc51c69b56a809 Mon Sep 17 00:00:00 2001 From: alejandrobailo Date: Thu, 19 Feb 2026 22:55:54 +0100 Subject: [PATCH] refactor(ui): remove old route-based provider flow --- .../add-credentials/page.tsx | 75 ----- .../connect-account/page.tsx | 7 - .../providers/(set-up-provider)/layout.tsx | 31 -- .../test-connection/page.tsx | 46 --- .../update-credentials/page.tsx | 76 ----- .../workflow/workflow-connect-llm.tsx | 62 +++- .../organizations/org-account-selection.tsx | 38 ++- .../organizations/org-connection-test.tsx | 57 ++-- .../organizations/org-launch-scan.tsx | 59 ++-- .../organizations/org-setup-form.tsx | 59 ++-- .../organizations/org-wizard-modal.tsx | 157 ---------- .../organizations/org-wizard-stepper.tsx | 196 ------------ ui/components/providers/workflow/index.ts | 1 - .../providers/workflow/vertical-steps.tsx | 294 ------------------ .../workflow/workflow-add-provider.tsx | 174 ----------- 15 files changed, 188 insertions(+), 1144 deletions(-) delete mode 100644 ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx delete mode 100644 ui/app/(prowler)/providers/(set-up-provider)/connect-account/page.tsx delete mode 100644 ui/app/(prowler)/providers/(set-up-provider)/layout.tsx delete mode 100644 ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx delete mode 100644 ui/app/(prowler)/providers/(set-up-provider)/update-credentials/page.tsx delete mode 100644 ui/components/providers/organizations/org-wizard-modal.tsx delete mode 100644 ui/components/providers/organizations/org-wizard-stepper.tsx delete mode 100644 ui/components/providers/workflow/vertical-steps.tsx delete mode 100644 ui/components/providers/workflow/workflow-add-provider.tsx 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 ( - <> - -
-
-
- -
-
- {children} -
-
- - ); -} 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 ( -
+

Organization Details

@@ -211,20 +240,12 @@ export function OrgSetupForm({ onBack, onNext }: OrgSetupFormProps) {

-
- - -
+ {isSubmitting && ( +
+ + Setting up organization... +
+ )}
); } 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 ( - - - -
- - Adding A Cloud Provider - - - Learn more - - -
-
- -
- {/* Left: Stepper */} -
- -
- - {/* Right: Step content */} -
- {currentStep === ORG_WIZARD_STEP.SETUP && ( - - )} - - {currentStep === ORG_WIZARD_STEP.VALIDATE && - validatePhase === VALIDATE_PHASE.DISCOVERY && ( - - )} - - {currentStep === ORG_WIZARD_STEP.VALIDATE && - validatePhase === VALIDATE_PHASE.SELECTION && ( - - )} - - {currentStep === ORG_WIZARD_STEP.VALIDATE && - validatePhase === VALIDATE_PHASE.TESTING && ( - - )} - - {currentStep === ORG_WIZARD_STEP.LAUNCH && ( - - )} -
-
-
-
- ); -} 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"); -}