"use client"
import { StructuredPricingSummary as StructuredPricingItem } from '../utils/compare-utils-stub'
// Using StructuredPricingItem from compare-utils instead of local interface
interface PricingStyleConfig {
priceTextSize: string
priceTextColor: string
secondaryTextSize: string
secondaryTextColor: string
showTildePrefix: boolean
fontFamily?: string
}
interface PricingDisplayProps {
pricing: StructuredPricingItem[] | string | StructuredPricingItem // Support both new structure and legacy string
className?: string
styleConfig?: PricingStyleConfig
}
// Default style configurations for different contexts
export const PRICING_STYLES = {
// Comparison table style (current default)
comparison: {
priceTextSize: 'text-h6',
priceTextColor: 'text-ods-text-primary',
secondaryTextSize: 'text-h6',
secondaryTextColor: 'text-ods-text-secondary',
showTildePrefix: false,
fontFamily: 'font-body'
},
// Vendor dropdown compact style (Figma design)
compact: {
priceTextSize: 'text-h6',
priceTextColor: 'text-ods-text-secondary',
secondaryTextSize: 'text-h6',
secondaryTextColor: 'text-ods-text-secondary',
showTildePrefix: true,
fontFamily: 'font-body'
},
// Card style for vendor cards
card: {
priceTextSize: 'text-h6',
priceTextColor: 'text-ods-text-primary',
secondaryTextSize: 'text-h6',
secondaryTextColor: 'text-ods-text-secondary',
showTildePrefix: true,
fontFamily: 'font-body'
}
} as const
/**
* Shared component for consistent pricing display with configurable styling
* Now accepts structured pricing data and style configuration for better control
*/
export function PricingDisplay({
pricing,
className = "",
styleConfig = PRICING_STYLES.comparison
}: PricingDisplayProps) {
// Legacy support for string input
if (typeof pricing === 'string') {
return
}
// Convert to array if single item
const pricingArray = Array.isArray(pricing) ? pricing : [pricing];
// Handle empty pricing
if (!pricingArray || pricingArray.length === 0) {
return (
No pricing data
)
}
// Handle single pricing item
if (pricingArray.length === 1) {
const item = pricingArray[0]
const price = item.ranges?.[0]?.min || 0;
const unit = item.ranges?.[0]?.unit;
return (
{formatPriceValue(price, styleConfig.showTildePrefix)}
{unit && (
/{unit}
)}
)
}
// Handle multiple pricing items
const priceValues = pricingArray.map(item => formatPriceValue(item.ranges?.[0]?.min || 0, styleConfig.showTildePrefix))
// Find the first item that has unit info
const itemWithUnit = pricingArray.find(item => item.ranges?.[0]?.unit)
return (
{priceValues.join(' | ')}
{itemWithUnit && itemWithUnit.ranges?.[0]?.unit && (
/{itemWithUnit.ranges[0].unit}
)}
)
}
/**
* Format price value consistently with configurable tilde prefix
*/
function formatPriceValue(price: number | 'Free' | 'Contact', showTildePrefix: boolean = false): string {
if (price === 'Free' || price === 'Contact') {
return price
}
if (price === 0) {
return 'Free'
}
return showTildePrefix ? `~$${price}` : `$${price}`
}
/**
* Legacy component for backward compatibility with string input
*/
function LegacyPricingDisplay({
pricing,
className = "",
styleConfig = PRICING_STYLES.comparison
}: {
pricing: string;
className?: string;
styleConfig?: PricingStyleConfig
}) {
// Handle "Free" case
if (pricing === 'Free' || pricing === 'No pricing data') {
return (
{pricing}
)
}
// Parse pricing string to separate main price from unit/cycle info
const parsePricing = (pricingStr: string) => {
// Handle comma-separated format like "$10/device/month, $120/device/year"
if (pricingStr.includes(', ')) {
// Split by comma and parse each part separately
const parts = pricingStr.split(', ')
const parsedParts = parts.map(part => {
const match = part.trim().match(/^(\$\d+(?:-\$\d+)?|\d+(?:-\d+)?|Free)(.*)$/)
if (match) {
const price = match[1].startsWith('$') ? match[1] : `$${match[1]}`
return {
price,
suffix: match[2]
}
}
return { price: part.trim(), suffix: '' }
})
// Reconstruct with proper styling
return {
mainValue: parsedParts.map(p => p.price).join(', '),
secondaryInfo: parsedParts.length > 0 && parsedParts[0].suffix ? parsedParts[0].suffix : ''
}
}
// Handle pipe-separated format like "$529/site/year | Free"
if (pricingStr.includes(' | ')) {
const parts = pricingStr.split(' | ')
const prices = parts.map(part => {
const match = part.trim().match(/^(\$\d+(?:-\$\d+)?|\d+(?:-\d+)?|Free)(.*)$/)
return match ? match[1] : part.trim()
})
return {
mainValue: prices.join(' | '),
secondaryInfo: ''
}
}
// Handle multi-cycle format like "$10/$120/device/month/year"
if (pricingStr.includes('/$')) {
// Find all price patterns
const pricePattern = /\$\d+(?:-\$\d+)?/g
const matches = [...pricingStr.matchAll(pricePattern)]
if (matches.length > 0) {
const lastMatch = matches[matches.length - 1]
const lastPriceEnd = lastMatch.index! + lastMatch[0].length
const mainValue = pricingStr.substring(0, lastPriceEnd)
const secondaryInfo = pricingStr.substring(lastPriceEnd)
return { mainValue, secondaryInfo }
}
}
// Handle single price format like "$529/site/year" or "$0-$529/site/year"
const singlePriceMatch = pricingStr.match(/^(\$\d+(?:-\$\d+)?|Free)(.*)$/)
if (singlePriceMatch) {
return {
mainValue: singlePriceMatch[1],
secondaryInfo: singlePriceMatch[2]
}
}
// Fallback - treat entire string as main value
return {
mainValue: pricingStr,
secondaryInfo: ''
}
}
const { mainValue, secondaryInfo } = parsePricing(pricing)
return (
{mainValue}
{secondaryInfo && (
{secondaryInfo}
)}
)
}
/**
* Utility function to format pricing for display in the PricingDisplay component
* This can be used to pre-process pricing strings if needed
*/
export function formatPricingForDisplay(pricing: string): string {
return pricing
}