Files
reui-blocks/blocks/pricing29.tsx
T

224 lines
6.6 KiB
TypeScript

import { Check, ChevronRight } from "lucide-react";
import { type SVGProps, useId } from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
const formatPrice = (value: string | number) =>
`${(typeof value === "string" ? Number(value) : value).toLocaleString("fa-IR")} ریال`;
type PricingTier = {
id: string;
name: string;
price: string;
description: string;
features: string[];
cta: {
text: string;
href: string;
};
};
const ITEMS: PricingTier[] = [
{
id: "starter",
name: "شروع",
price: formatPrice(0),
description: "رایگان برای همه",
features: ["اعضای نامحدود", "۲۵۰ تراکنش", "بدون پشتیبانی"],
cta: {
text: "رایگان شروع رایگان",
href: "/signup",
},
},
{
id: "basic",
name: "پایه",
price: formatPrice(299900),
description: "به ازای هر کاربر در ماه",
features: [
"همه‌چیز در طرح رایگان، به‌علاوه:",
"هوش مصنوعی نوافروش",
"تیم‌های نامحدود",
],
cta: {
text: "۷ روز رایگان",
href: "/signup",
},
},
{
id: "enterprise",
name: "سازمانی",
price: "سفارشی",
description: "قیمت‌گذاری سفارشی",
features: [
"همه‌چیز در طرح پایه، به‌علاوه:",
"کنترل‌های امنیتی پیشرفته",
"پشتیبانی مهاجرت",
],
cta: {
text: "رزرو دمو",
href: "/",
},
},
];
export const Pricing29 = () => {
return (
<section className="relative overflow-hidden py-32 text-center">
<div className="container">
<h1 className="text-xl font-semibold tracking-tight md:text-2xll lg:text-2xll">
تعرفه‌ها
</h1>
<div className="mx-auto mt-4 max-w-[45rem] space-y-2">
<p className="text-xl text-muted-foreground md:text-3xl">
نوافروش را رایگان با کل تیم خود استفاده کنید. با ارتقای طرح،
امکانات پیشرفته‌تر را فعال کنید.
</p>
</div>
<div className="relative mt-8 overflow-hidden md:mt-12 lg:mt-20">
<div className="absolute inset-0 hidden rounded-3xl bg-linear-to-r from-primary to-primary/80 md:block">
<PlusSigns className="h-full w-full text-foreground/[0.05]" />
</div>
<div className="relative space-y-6 md:grid md:grid-cols-3 md:space-y-0 md:divide-s md:divide-background/20 md:p-6 lg:p-8">
{ITEMS.map((tier, index) => (
<PricingCard
key={tier.id}
tier={tier}
isHighlighted={index === 1}
/>
))}
</div>
</div>
<div className="absolute -inset-40 z-[-1] [mask-image:radial-gradient(circle_at_center,black_0%,black_20%,transparent_75%)]">
<PlusSigns className="size-full text-foreground/[0.05]" />
</div>
</div>
</section>
);
};
function PricingCard({
tier,
isHighlighted,
}: {
tier: PricingTier;
isHighlighted: boolean;
}) {
const styles = {
card: cn(
"flex flex-col gap-6 rounded-xl p-6 sm:rounded-2xl md:rounded-none lg:p-8",
isHighlighted
? "max-md:from-primary max-md:to-primary/80 max-md:bg-linear-to-r"
: "bg-background max-md:border",
"md:bg-transparent",
),
title: cn(
"font-mono text-sm tracking-widest",
isHighlighted ? "text-background/70" : "text-foreground/70",
"md:text-background/70",
),
price: cn(
"text-2xll font-semibold tracking-tight",
isHighlighted ? "text-background" : "text-foreground",
"md:text-background",
),
description: cn(
"mt-2 text-xl font-medium",
isHighlighted ? "text-background/70" : "text-foreground/70",
"md:text-background/70",
),
features: cn(
"space-y-3 text-sm",
isHighlighted ? "text-background/70" : "text-foreground/70",
"md:text-background/70",
),
button: cn(
"group border-foreground/20 relative w-full",
"after:from-border after:via-border after:absolute after:inset-0 after:bg-linear-to-t after:to-transparent after:content-[''] after:group-hover:opacity-100 isolate after:z-[-1]",
"md:border-background/40 md:text-background md:bg-transparent md:after:opacity-0",
isHighlighted &&
"md:bg-background md:text-primary hover:md:bg-background/90",
),
};
return (
<div className={styles.card}>
<h3 className={styles.title}>{tier.name}</h3>
<div>
<p className={styles.price}>{tier.price}</p>
<p className={styles.description}>{tier.description}</p>
</div>
<ul className={styles.features}>
{tier.features.map((feature) => (
<li key={feature} className="flex items-center gap-2">
<Check className="size-4 shrink-0" />
<span>{feature}</span>
</li>
))}
</ul>
<div className="flex flex-1 items-end">
<Button
asChild
variant={isHighlighted ? "secondary" : "outline"}
size="lg"
className={styles.button}
>
<a href={tier.cta.href}>
{tier.cta.text}
<ChevronRight className="ms-1 size-4 transition-transform group-hover:translate-x-0.5 rtl:rotate-180" />
</a>
</Button>
</div>
</div>
);
}
interface PlusSignsProps extends SVGProps<SVGSVGElement> {
className?: string;
}
const PlusSigns = ({ className, ...props }: PlusSignsProps) => {
const GAP = 16;
const STROKE_WIDTH = 1;
const PLUS_SIZE = 6;
const id = useId();
const patternId = `plus-pattern-${id}`;
return (
<svg width={GAP * 2} height={GAP * 2} className={className} {...props}>
<defs>
<pattern
id={patternId}
x="0"
y="0"
width={GAP}
height={GAP}
patternUnits="userSpaceOnUse"
>
<line
x1={GAP / 2}
y1={(GAP - PLUS_SIZE) / 2}
x2={GAP / 2}
y2={(GAP + PLUS_SIZE) / 2}
stroke="currentColor"
strokeWidth={STROKE_WIDTH}
/>
<line
x1={(GAP - PLUS_SIZE) / 2}
y1={GAP / 2}
x2={(GAP + PLUS_SIZE) / 2}
y2={GAP / 2}
stroke="currentColor"
strokeWidth={STROKE_WIDTH}
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${patternId})`} />
</svg>
);
};