100 lines
3.8 KiB
TypeScript
100 lines
3.8 KiB
TypeScript
import React from 'react';
|
|
import { useTheme } from '@/lib/theme-context';
|
|
|
|
interface PricingCardProps {
|
|
title: string;
|
|
price: string;
|
|
billingPeriod: string;
|
|
features: string[];
|
|
ctaText: string;
|
|
ctaHref: string;
|
|
isPopular?: boolean;
|
|
}
|
|
|
|
const PricingCard: React.FC<PricingCardProps> = ({
|
|
title,
|
|
price,
|
|
billingPeriod,
|
|
features,
|
|
ctaText,
|
|
ctaHref,
|
|
isPopular = false,
|
|
}) => {
|
|
const { theme } = useTheme();
|
|
|
|
// Add ARIA label for screen readers
|
|
const cardAriaLabel = `${title} pricing plan - $${price}/${billingPeriod}`;
|
|
|
|
const cardBg = theme === 'dark' ? 'bg-gray-800' : 'bg-white';
|
|
const cardText = theme === 'dark' ? 'text-gray-100' : 'text-gray-900';
|
|
const cardBorder = theme === 'dark' ? 'border-gray-700' : 'border-gray-200';
|
|
const cardShadow = theme === 'dark' ? 'shadow-lg' : 'shadow-sm';
|
|
const cardHover = theme === 'dark' ? 'hover:bg-gray-700' : 'hover:bg-indigo-600';
|
|
const cardFocus = theme === 'dark' ? 'focus:ring-indigo-500' : 'focus:ring-indigo-500';
|
|
const cardPopular = theme === 'dark' ? 'bg-indigo-700' : 'bg-indigo-600';
|
|
const cardFeatureText = theme === 'dark' ? 'text-gray-300' : 'text-gray-700';
|
|
const cardFeatureIcon = theme === 'dark' ? 'text-green-400' : 'text-green-500';
|
|
const cardHoverShadow = theme === 'dark' ? 'hover:shadow-xl' : 'hover:shadow-2xl';
|
|
const cardTransition = 'transition-all duration-300';
|
|
|
|
return (
|
|
<div
|
|
className={`flex flex-col ${cardBg} border ${cardBorder} rounded-xl ${cardShadow} overflow-hidden ${cardTransition} ${cardHoverShadow}`}
|
|
role="article"
|
|
aria-label={cardAriaLabel}
|
|
tabIndex={0} // Make the card focusable
|
|
>
|
|
{isPopular && (
|
|
<div
|
|
className={`${cardPopular} text-white text-xs font-semibold px-3 py-1.5 rounded-t-xl`}
|
|
>
|
|
Popular
|
|
</div>
|
|
)}
|
|
<div className="p-6">
|
|
<h3 className={`text-2xl font-bold ${cardText}`}>{title}</h3>
|
|
<p className="mt-2 text-sm text-gray-400">
|
|
{title === 'Start' && 'The perfect starting place for your web app or personal project.'}
|
|
{title === 'Pro' && 'Everything you need to build and scale your app.'}
|
|
{title === 'Premium' && 'Critical security, performance, observability, platform SLAs, and support.'}
|
|
</p>
|
|
<p className="mt-6">
|
|
<span className="text-4xl font-extrabold">{price}</span>
|
|
<span className="text-base font-medium text-gray-400 ml-1">/{billingPeriod}</span>
|
|
</p>
|
|
<a
|
|
href={ctaHref}
|
|
className={`mt-6 block w-full py-3 px-4 border border-transparent rounded-md shadow text-center text-white bg-indigo-600 ${cardHover} focus:outline-none focus:ring-2 focus:ring-offset-2 ${cardFocus} ${cardTransition}`}
|
|
aria-label={`${ctaText} for ${title} plan`}
|
|
>
|
|
{ctaText}
|
|
</a>
|
|
</div>
|
|
<div className={`border-t ${cardBorder} p-6`}>
|
|
<h4 className={`text-sm font-medium ${cardText}`}>What's included:</h4>
|
|
<ul className="mt-4 space-y-3">
|
|
{features.map((feature, index) => (
|
|
<li key={index} className="flex items-start">
|
|
<svg
|
|
className={`h-5 w-5 ${cardFeatureIcon} flex-shrink-0`}
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
<span className={`ml-3 text-sm ${cardFeatureText}`}>{feature}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(PricingCard); |