"use client"; import { Info } from "lucide-react"; import { usePathname, useSearchParams } from "next/navigation"; import { Suspense, useState } from "react"; import { OnboardingTrigger, PageReady } from "@/components/onboarding"; import { AddProviderButton } from "@/components/providers/add-provider-button"; import { MutedFindingsConfigButton } from "@/components/providers/muted-findings-config-button"; import { NoProvidersAdded } from "@/components/providers/no-providers-added"; import { ProvidersAccountsTable } from "@/components/providers/providers-accounts-table"; import { ProvidersFilters } from "@/components/providers/providers-filters"; import { ProviderWizardModal } from "@/components/providers/wizard"; import type { OrgWizardInitialData, ProviderWizardInitialData, } from "@/components/providers/wizard/types"; import { Alert, AlertDescription } from "@/components/shadcn/alert"; import { useMountEffect } from "@/hooks/use-mount-effect"; import { getFlowById } from "@/lib/onboarding"; import { dispatchProviderFunnel, PROVIDER_FUNNEL_STEP, WIZARD_OPEN_SOURCE, type WizardOpenSource, } from "@/lib/provider-funnel/provider-funnel-events"; import { ADD_PROVIDER_SEARCH_PARAM, ADD_PROVIDER_SEARCH_VALUE, ADD_PROVIDER_SOURCE_PARAM, resolveAddProviderSource, } from "@/lib/providers-navigation"; import { ADD_PROVIDER_TOUR_TARGETS, addProviderTour, } from "@/lib/tours/add-provider.tour"; import { advanceActiveTourWhenReady, getTourTargetSelector, } from "@/lib/tours/use-driver-tour"; import type { FilterOption, MetaDataProps, ProviderProps } from "@/types"; import type { ProviderGroup } from "@/types/components"; import { HIERARCHY_STATUS, type HierarchyStatus, type ProvidersTableRow, } from "@/types/providers-table"; import type { ScanConfigurationData, ScanConfigurationListStatus, } from "@/types/scan-configurations"; import type { ScanScheduleCapability } from "@/types/schedules"; const addProviderFlow = getFlowById("add-provider")!; // Softer overlay for this onboarding tour — a heavy dim over the wizard feels harsh // while the user fills the form. Module-level constant keeps the driver config stable // across renders (useDriverTour recreates the driver when configOverrides identity // changes). const ADD_PROVIDER_TOUR_CONFIG = { overlayOpacity: 0.45 } as const; // The tour's "trigger" step auto-advances when the wizard opens; this is the anchor // it waits for (the provider-type selector inside the wizard). const PROVIDER_TYPE_TOUR_SELECTOR = getTourTargetSelector( addProviderTour.id, ADD_PROVIDER_TOUR_TARGETS.PROVIDER_TYPE, ); interface ProvidersAccountsViewProps { isCloud: boolean; filters: FilterOption[]; metadata?: MetaDataProps; providers: ProviderProps[]; providerGroups?: ProviderGroup[]; rows: ProvidersTableRow[]; /** Cloud overlay seam for provider-creation scan launch. */ scanScheduleCapability?: ScanScheduleCapability; /** All scan configurations in the tenant, for the provider row's associate/ * disassociate action (Cloud-only). */ scanConfigs?: ScanConfigurationData[]; scanConfigStatus?: ScanConfigurationListStatus; isScanLimitReached?: boolean; hierarchyStatus?: HierarchyStatus; } export function ProvidersAccountsView({ isCloud, filters, metadata, providers, providerGroups = [], rows, scanScheduleCapability, scanConfigs, scanConfigStatus, isScanLimitReached, hierarchyStatus = HIERARCHY_STATUS.AVAILABLE, }: ProvidersAccountsViewProps) { const pathname = usePathname(); const searchParams = useSearchParams(); const hasNoProviders = providers.length === 0; const shouldOpenProviderWizardFromUrl = searchParams.get(ADD_PROVIDER_SEARCH_PARAM) === ADD_PROVIDER_SEARCH_VALUE; const [isProviderWizardOpen, setIsProviderWizardOpen] = useState( () => shouldOpenProviderWizardFromUrl, ); const [providerWizardInitialData, setProviderWizardInitialData] = useState< ProviderWizardInitialData | undefined >(undefined); const [orgWizardInitialData, setOrgWizardInitialData] = useState< OrgWizardInitialData | undefined >(undefined); const signalWizardOpened = (source: WizardOpenSource) => dispatchProviderFunnel({ step: PROVIDER_FUNNEL_STEP.WIZARD_OPENED, source, }); // A URL-opened wizard never goes through openProviderWizard, so signal it on mount. useMountEffect(() => { if (!shouldOpenProviderWizardFromUrl) return; signalWizardOpened( resolveAddProviderSource(searchParams.get(ADD_PROVIDER_SOURCE_PARAM)), ); }); const openProviderWizard = ( source: WizardOpenSource, initialData?: ProviderWizardInitialData, ) => { signalWizardOpened(source); setOrgWizardInitialData(undefined); setProviderWizardInitialData(initialData); setIsProviderWizardOpen(true); // If the add-provider tour is on its "trigger" step, opening the wizard is the // advance signal: move to the provider-type step once it mounts. No-op otherwise. advanceActiveTourWhenReady(PROVIDER_TYPE_TOUR_SELECTOR); }; const openOrganizationWizard = (initialData: OrgWizardInitialData) => { setProviderWizardInitialData(undefined); setOrgWizardInitialData(initialData); setIsProviderWizardOpen(true); }; const handleWizardOpenChange = (open: boolean) => { setIsProviderWizardOpen(open); if (open) return; setProviderWizardInitialData(undefined); setOrgWizardInitialData(undefined); // Remove ?addProvider via History API (not router.replace) to avoid an RSC refetch; // revalidatePath in the creation actions already refreshes the table. if (searchParams.has(ADD_PROVIDER_SEARCH_PARAM)) { const params = new URLSearchParams(searchParams.toString()); params.delete(ADD_PROVIDER_SEARCH_PARAM); params.delete(ADD_PROVIDER_SOURCE_PARAM); const query = params.toString(); window.history.replaceState( null, "", query ? `${pathname}?${query}` : pathname, ); } }; return ( <> {/* Suspense required: OnboardingTrigger reads useSearchParams */} {/* Signals the navbar that this route's data has loaded (enables the replay icon). */} {hasNoProviders ? ( openProviderWizard(WIZARD_OPEN_SOURCE.EMPTY_STATE) } ctaTourId="add-provider-trigger" /> ) : (
{hierarchyStatus === HIERARCHY_STATUS.UNAVAILABLE && ( Organization grouping is incomplete. Some providers may appear ungrouped. )} openProviderWizard(WIZARD_OPEN_SOURCE.PAGE_BUTTON) } /> } /> openProviderWizard(WIZARD_OPEN_SOURCE.ROW_ACTION, initialData) } onOpenOrganizationWizard={openOrganizationWizard} />
)} ); }