-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
763 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,16 @@ | ||
{ | ||
"name": "pricing", | ||
"type": "registry:block", | ||
"type": "registry:ui", | ||
"code": "\"use client\";\n\n// ... (paste the entire component code here)\n", | ||
"files": [ | ||
{ | ||
"path": "section/pricing.tsx", | ||
"type": "registry:block", | ||
"content": "\"use client\";\n\nimport Section from \"@/components/section\";\nimport { buttonVariants } from \"@/components/ui/button\";\nimport { Label } from \"@/components/ui/label\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { siteConfig } from \"@/lib/config\";\nimport useWindowSize from \"@/lib/hooks/use-window-size\";\nimport { cn } from \"@/lib/utils\";\nimport { motion } from \"framer-motion\";\nimport { Check } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useState, useRef } from \"react\";\nimport { FaStar } from \"react-icons/fa\";\nimport confetti from \"canvas-confetti\";\nimport NumberFlow from \"@number-flow/react\";\n\nexport default function Pricing() {\n const [isMonthly, setIsMonthly] = useState(true);\n const { isDesktop } = useWindowSize();\n const switchRef = useRef<HTMLButtonElement>(null);\n\n const handleToggle = (checked: boolean) => {\n setIsMonthly(!checked);\n if (checked && switchRef.current) {\n const rect = switchRef.current.getBoundingClientRect();\n const x = rect.left + rect.width / 2;\n const y = rect.top + rect.height / 2;\n\n confetti({\n particleCount: 50,\n spread: 60,\n origin: {\n x: x / window.innerWidth,\n y: y / window.innerHeight,\n },\n colors: [\n \"hsl(var(--primary))\",\n \"hsl(var(--accent))\",\n \"hsl(var(--secondary))\",\n \"hsl(var(--muted))\",\n ],\n ticks: 200,\n gravity: 1.2,\n decay: 0.94,\n startVelocity: 30,\n shapes: [\"circle\"],\n });\n }\n };\n\n return (\n <Section>\n <div className=\"text-center space-y-4 mb-12\">\n <h2 className=\"text-4xl font-bold tracking-tight sm:text-5xl\">\n Simple, Transparent Pricing\n </h2>\n <p className=\"text-muted-foreground text-lg\">\n Choose the plan that works for you\n <br />\n All plans include access to our platform, lead generation tools, and\n dedicated support.\n </p>\n </div>\n\n <div className=\"flex justify-center mb-10\">\n <label className=\"relative inline-flex items-center cursor-pointer\">\n <Label>\n <Switch\n ref={switchRef}\n checked={!isMonthly}\n onCheckedChange={handleToggle}\n className=\"relative\"\n />\n </Label>\n </label>\n <span className=\"ml-2 font-semibold\">\n Annual billing <span className=\"text-primary\">(Save 20%)</span>\n </span>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-3 sm:2 gap-4\">\n {[\n {\n name: \"STARTER\",\n price: \"50\",\n yearlyPrice: \"40\",\n period: \"per month\",\n features: [\n \"Up to 10 projects\",\n \"Basic analytics\",\n \"48-hour support response time\",\n \"Limited API access\",\n \"Community support\",\n ],\n description: \"Perfect for individuals and small projects\",\n buttonText: \"Start Free Trial\",\n href: \"/sign-up\",\n isPopular: false,\n },\n {\n name: \"PROFESSIONAL\",\n price: \"99\",\n yearlyPrice: \"79\",\n period: \"per month\",\n features: [\n \"Unlimited projects\",\n \"Advanced analytics\",\n \"24-hour support response time\",\n \"Full API access\",\n \"Priority support\",\n \"Team collaboration\",\n \"Custom integrations\",\n ],\n description: \"Ideal for growing teams and businesses\",\n buttonText: \"Get Started\",\n href: \"/sign-up\",\n isPopular: true,\n },\n {\n name: \"ENTERPRISE\",\n price: \"299\",\n yearlyPrice: \"239\",\n period: \"per month\",\n features: [\n \"Everything in Professional\",\n \"Custom solutions\",\n \"Dedicated account manager\",\n \"1-hour support response time\",\n \"SSO Authentication\",\n \"Advanced security\",\n \"Custom contracts\",\n \"SLA agreement\",\n ],\n description: \"For large organizations with specific needs\",\n buttonText: \"Contact Sales\",\n href: \"/contact\",\n isPopular: false,\n },\n ].map((plan, index) => (\n <motion.div\n key={index}\n initial={{ y: 50, opacity: 1 }}\n whileInView={\n isDesktop\n ? {\n y: plan.isPopular ? -20 : 0,\n opacity: 1,\n x: index === 2 ? -30 : index === 0 ? 30 : 0,\n scale: index === 0 || index === 2 ? 0.94 : 1.0,\n }\n : {}\n }\n viewport={{ once: true }}\n transition={{\n duration: 1.6,\n type: \"spring\",\n stiffness: 100,\n damping: 30,\n delay: 0.4,\n opacity: { duration: 0.5 },\n }}\n className={cn(\n `rounded-2xl border-[1px] p-6 bg-background text-center lg:flex lg:flex-col lg:justify-center relative`,\n plan.isPopular ? \"border-primary border-2\" : \"border-border\",\n \"flex flex-col\",\n !plan.isPopular && \"mt-5\",\n index === 0 || index === 2\n ? \"z-0 transform translate-x-0 translate-y-0 -translate-z-[50px] rotate-y-[10deg]\"\n : \"z-10\",\n index === 0 && \"origin-right\",\n index === 2 && \"origin-left\"\n )}\n >\n {plan.isPopular && (\n <div className=\"absolute top-0 right-0 bg-primary py-0.5 px-2 rounded-bl-xl rounded-tr-xl flex items-center\">\n <FaStar className=\"text-primary-foreground\" />\n <span className=\"text-primary-foreground ml-1 font-sans font-semibold\">\n Popular\n </span>\n </div>\n )}\n <div className=\"flex-1 flex flex-col\">\n <p className=\"text-base font-semibold text-muted-foreground\">\n {plan.name}\n </p>\n <div className=\"mt-6 flex items-center justify-center gap-x-2\">\n <span className=\"text-5xl font-bold tracking-tight text-foreground\">\n <NumberFlow\n value={\n isMonthly ? Number(plan.price) : Number(plan.yearlyPrice)\n }\n format={{\n style: \"currency\",\n currency: \"USD\",\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n }}\n formatter={(value) => `$${value}`}\n transformTiming={{\n duration: 500,\n easing: \"ease-out\",\n }}\n willChange\n className=\"font-variant-numeric: tabular-nums\"\n />\n </span>\n {plan.period !== \"Next 3 months\" && (\n <span className=\"text-sm font-semibold leading-6 tracking-wide text-muted-foreground\">\n / {plan.period}\n </span>\n )}\n </div>\n\n <p className=\"text-xs leading-5 text-muted-foreground\">\n {isMonthly ? \"billed monthly\" : \"billed annually\"}\n </p>\n\n <ul className=\"mt-5 gap-2 flex flex-col\">\n {plan.features.map((feature, idx) => (\n <li key={idx} className=\"flex items-center\">\n <Check className=\"mr-2 h-4 w-4 text-primary\" />\n <span>{feature}</span>\n </li>\n ))}\n </ul>\n\n <hr className=\"w-full my-4\" />\n\n <Link\n href={plan.href}\n className={cn(\n buttonVariants({\n variant: \"outline\",\n }),\n \"group relative w-full gap-2 overflow-hidden text-lg font-semibold tracking-tighter\",\n \"transform-gpu ring-offset-current transition-all duration-300 ease-out hover:ring-2 hover:ring-primary hover:ring-offset-1 hover:bg-primary hover:text-primary-foreground\",\n plan.isPopular\n ? \"bg-primary text-primary-foreground\"\n : \"bg-background text-foreground\"\n )}\n >\n {plan.buttonText}\n </Link>\n <p className=\"mt-6 text-xs leading-5 text-muted-foreground\">\n {plan.description}\n </p>\n </div>\n </motion.div>\n ))}\n </div>\n </Section>\n );\n}\n" | ||
"path": "components/prismui/pricing.tsx", | ||
"type": "registry:ui", | ||
"content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Check } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport NumberFlow from \"@/components/prismui/number-flow\";\nimport confetti from \"canvas-confetti\";\n\ninterface PricingPlan {\n name: string;\n description: string;\n price: {\n monthly: number;\n yearly: number;\n };\n features: string[];\n isPopular?: boolean;\n buttonText?: string;\n buttonVariant?: \"default\" | \"outline\";\n}\n\ninterface PricingProps {\n title?: string;\n description?: string;\n plans: PricingPlan[];\n className?: string;\n}\n\nexport default function Pricing({\n title = \"Simple, transparent pricing\",\n description = \"Choose the plan that's right for you\",\n plans,\n className,\n}: PricingProps) {\n const [isYearly, setIsYearly] = useState(false);\n\n const handleToggle = () => {\n setIsYearly(!isYearly);\n if (!isYearly) {\n confetti({\n particleCount: 100,\n spread: 70,\n origin: { y: 0.6 },\n });\n }\n };\n\n return (\n <section className={cn(\"py-16 md:py-24\", className)}>\n <div className=\"container px-4 md:px-6\">\n <div className=\"flex flex-col items-center space-y-4 text-center\">\n <h2 className=\"text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl\">\n {title}\n </h2>\n <p className=\"max-w-[700px] text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed dark:text-gray-400\">\n {description}\n </p>\n <div className=\"flex items-center space-x-2\">\n <span className=\"text-sm font-medium\">Monthly</span>\n <Switch checked={isYearly} onCheckedChange={handleToggle} />\n <span className=\"text-sm font-medium\">Yearly</span>\n {isYearly && (\n <Badge variant=\"secondary\" className=\"ml-2\">\n Save 20%\n </Badge>\n )}\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-6 mt-12 md:grid-cols-3 lg:gap-8\">\n {plans.map((plan, index) => (\n <motion.div\n key={plan.name}\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: index * 0.1 }}\n className=\"relative\"\n >\n <Card\n className={cn(\n \"flex h-full flex-col p-6\",\n plan.isPopular && \"border-primary shadow-lg\"\n )}\n >\n {plan.isPopular && (\n <Badge\n className=\"absolute -top-2 right-4\"\n variant=\"secondary\"\n >\n Most Popular\n </Badge>\n )}\n <div className=\"flex-1 space-y-4\">\n <h3 className=\"text-xl font-bold\">{plan.name}</h3>\n <p className=\"text-sm text-gray-500 dark:text-gray-400\">\n {plan.description}\n </p>\n <div className=\"flex items-baseline\">\n <span className=\"text-3xl font-bold\">$</span>\n <AnimatePresence mode=\"wait\">\n <NumberFlow\n key={isYearly ? \"yearly\" : \"monthly\"}\n value={\n isYearly ? plan.price.yearly : plan.price.monthly\n }\n format={{ minimumFractionDigits: 0 }}\n />\n </AnimatePresence>\n <span className=\"ml-1 text-sm text-gray-500\">\n /{isYearly ? \"year\" : \"month\"}\n </span>\n </div>\n <ul className=\"space-y-2\">\n {plan.features.map((feature) => (\n <li\n key={feature}\n className=\"flex items-center text-sm text-gray-500\"\n >\n <Check className=\"mr-2 h-4 w-4 text-primary\" />\n {feature}\n </li>\n ))}\n </ul>\n </div>\n <Button\n className=\"mt-6 w-full\"\n variant={plan.buttonVariant || \"default\"}\n >\n {plan.buttonText || \"Get Started\"}\n </Button>\n </Card>\n </motion.div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n" | ||
} | ||
], | ||
"dependencies": [ | ||
"@/components/ui/button" | ||
"framer-motion", | ||
"canvas-confetti" | ||
] | ||
} |
Oops, something went wrong.