224 lines
6.6 KiB
TypeScript
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>
|
|
);
|
|
};
|