"use client"; import type { SVGProps } from "react"; import React, { type ReactNode, useEffect, useMemo, useState } from "react"; import { cx } from "../lib/utils"; import { OnboardingButton } from "../primitives/onboarding-button"; import { OnboardingCard } from "../primitives/onboarding-card"; import { OnboardingInput } from "../primitives/onboarding-input"; import { OnboardingLabel } from "../primitives/onboarding-label"; import { OnboardingRadioCardGroup, OnboardingRadioCardItem, } from "../primitives/onboarding-radio-card"; import { OnboardingRadioGroup, OnboardingRadioGroupItem, } from "../primitives/onboarding-radio-group"; import { OnboardingSelect, OnboardingSelectContent, OnboardingSelectItem, OnboardingSelectTrigger, OnboardingSelectValue, } from "../primitives/onboarding-select"; import { OnboardingSlider } from "../primitives/onboarding-slider"; export interface Region { value: string; label: string; multiplier: number; } export interface CloudProvider { id: string; name: string; icon?: ReactNode; regions: Region[]; basePrice: number; } export interface InfrastructureConfig { cloudProvider: string; region: string; storageVolume: number; compression: boolean; activeHours: number; } export interface InfrastructureStepProps { /** Title displayed at the top of the step */ title?: string; /** Description text below the title */ description?: string; /** Array of cloud providers to choose from */ cloudProviders: CloudProvider[]; /** Initial configuration values */ defaultConfig?: Partial; /** Storage volume limits */ storageLimits?: { min: number; max: number }; /** Called when configuration changes */ onConfigChange?: (config: InfrastructureConfig) => void; /** Called when the user submits the form */ onSubmit: (config: InfrastructureConfig) => void | Promise; /** Custom price calculator - receives config and returns formatted string */ calculatePrice?: ( config: InfrastructureConfig, provider: CloudProvider, ) => string; /** Text for the submit button */ submitText?: string; /** Text shown while submitting */ loadingText?: string; /** Optional back button config */ backButton?: { text: string; onClick: () => void; }; } // Default icons const MicrosoftAzure = (props: SVGProps) => ( ); const AmazonWebServices = (props: SVGProps) => ( ); export const defaultCloudProviderIcons: Record< string, React.ComponentType> > = { aws: AmazonWebServices, azure: MicrosoftAzure, }; function defaultPriceCalculator( config: InfrastructureConfig, provider: CloudProvider, ): string { const activeHourMultiplier = 0.05; const compressionMultiplier = config.compression ? 0.7 : 1.0; const selectedRegion = provider.regions.find( (r) => r.value === config.region, ); const regionMultiplier = selectedRegion?.multiplier || 1.0; const storagePrice = provider.basePrice * config.storageVolume * regionMultiplier * compressionMultiplier; const activeHoursPrice = config.activeHours * activeHourMultiplier; const totalPricePerDay = storagePrice + activeHoursPrice; const totalPricePerMonth = totalPricePerDay * 30; const priceRangeLow = (totalPricePerMonth * 0.8 * 10).toFixed(0); const priceRangeHigh = (totalPricePerMonth * 1.2 * 10).toFixed(0); return `${priceRangeLow} - ${priceRangeHigh} USD`; } export function InfrastructureStep({ title = "Create a new compute cluster", description = "You have full control over the resources provisioned.", cloudProviders, defaultConfig, storageLimits = { min: 6, max: 128 }, onConfigChange, onSubmit, calculatePrice = defaultPriceCalculator, submitText = "Continue", loadingText = "Submitting...", backButton, }: InfrastructureStepProps) { const [cloudProvider, setCloudProvider] = useState( defaultConfig?.cloudProvider || cloudProviders[0]?.id || "", ); const [region, setRegion] = useState(defaultConfig?.region || ""); const [activeHours, setActiveHours] = useState([ defaultConfig?.activeHours ?? 6, ]); const [storageVolume, setStorageVolume] = useState( defaultConfig?.storageVolume ?? storageLimits.min, ); const [compression, setCompression] = useState( defaultConfig?.compression ? "true" : "false", ); const [loading, setLoading] = useState(false); const currentProvider = useMemo( () => cloudProviders.find((p) => p.id === cloudProvider), [cloudProviders, cloudProvider], ); useEffect(() => { if (currentProvider && currentProvider.regions.length > 0) { setRegion(currentProvider.regions[0].value); } }, [currentProvider]); const config: InfrastructureConfig = useMemo( () => ({ cloudProvider, region, storageVolume, compression: compression === "true", activeHours: activeHours[0], }), [cloudProvider, region, storageVolume, compression, activeHours], ); useEffect(() => { onConfigChange?.(config); }, [config, onConfigChange]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { await onSubmit(config); } finally { setLoading(false); } }; const price = currentProvider ? calculatePrice(config, currentProvider) : ""; const isValid = cloudProvider && region && activeHours[0] !== undefined && storageVolume && compression; return (

{title}

{description}

Cloud provider {cloudProviders.map((provider) => { const Icon = provider.icon || defaultCloudProviderIcons[provider.id]; return (
{Icon && (typeof Icon === "function" ? (

{provider.regions.length} regions available

); })}
Storage (GB) setStorageVolume(Number(e.target.value))} aria-describedby="storage-description" />

Enter storage volume between {storageLimits.min} and{" "} {storageLimits.max} GB

Would you like to auto compress your data?
Yes
No
Region {currentProvider?.regions.map((option) => ( {option.label} ))}
Active hours per day

Estimated monthly cost

{price}

{backButton && ( {backButton.text} )} {loading ? loadingText : submitText}
); }