"use client"; import type React from "react"; import { 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 { OnboardingSlider } from "../primitives/onboarding-slider"; export interface ScaleField { /** Unique identifier for the field */ id: string; /** Label displayed above the input */ label: string; /** Optional description text */ description?: string; /** Minimum value */ min: number; /** Maximum value */ max: number; /** Step increment (default: 1) */ step?: number; /** Default/initial value */ defaultValue?: number; /** Unit label (e.g., "requests/mo", "GB", "users") */ unit?: string; /** Custom value formatter for display (e.g., "10K", "1M") */ formatValue?: (value: number) => string; /** Whether to show a slider (default: true) */ showSlider?: boolean; } export interface ScaleInputStepProps { /** Title displayed at the top of the step */ title?: string; /** Description text below the title */ description?: string; /** Array of scale fields to display */ fields: ScaleField[]; /** Called when any value changes */ onValuesChange?: (values: Record) => void; /** Called when the user submits the form */ onSubmit: (values: Record) => void | Promise; /** Text for the submit button */ submitText?: string; /** Text shown while submitting */ loadingText?: string; /** Optional back button config */ backButton?: { text: string; onClick: () => void; }; /** Optional summary card config */ summary?: { label: string; calculate: (values: Record) => string; }; } function defaultFormatValue(value: number): string { if (value >= 1_000_000_000) { return `${(value / 1_000_000_000).toFixed(1)}B`; } if (value >= 1_000_000) { return `${(value / 1_000_000).toFixed(1)}M`; } if (value >= 1_000) { return `${(value / 1_000).toFixed(1)}K`; } return value.toString(); } interface FieldInputProps { field: ScaleField; value: number; onChange: (value: number) => void; animationDelay: number; } function FieldInput({ field, value, onChange, animationDelay, }: FieldInputProps) { const displayValue = field.formatValue ? field.formatValue(value) : defaultFormatValue(value); const showSlider = field.showSlider !== false; return (
{field.label} {field.unit && ( {field.unit} )}
{field.description && (

{field.description}

)}
{showSlider ? ( <> onChange(newValue)} min={field.min} max={field.max} step={field.step || 1} aria-valuetext={`${displayValue} ${field.unit || ""}`} className="flex-1" />
{displayValue}
) : ( onChange(Number(e.target.value))} className="max-w-32" /> )}
); } export function ScaleInputStep({ title = "Configure your scale", description = "Help us understand your expected usage.", fields, onValuesChange, onSubmit, submitText = "Continue", loadingText = "Submitting...", backButton, summary, }: ScaleInputStepProps) { const [values, setValues] = useState>(() => { const initial: Record = {}; fields.forEach((field) => { initial[field.id] = field.defaultValue ?? field.min; }); return initial; }); const [loading, setLoading] = useState(false); useEffect(() => { onValuesChange?.(values); }, [values, onValuesChange]); const handleFieldChange = (fieldId: string, value: number) => { setValues((prev) => ({ ...prev, [fieldId]: value, })); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); try { await onSubmit(values); } finally { setLoading(false); } }; const summaryValue = useMemo(() => { if (summary) { return summary.calculate(values); } return null; }, [summary, values]); const isValid = fields.every((field) => { const value = values[field.id]; return value >= field.min && value <= field.max; }); return (

{title}

{description}

{fields.map((field, index) => ( handleFieldChange(field.id, value)} animationDelay={200 + index * 150} /> ))} {summary && summaryValue && (

{summary.label}

{summaryValue}

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