Files
netrunner-landing/components/sections/Pricing.tsx
T

282 lines
10 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { CheckIcon, Loader2 } from "lucide-react";
import { Dictionary } from "@/lib/IDict";
import { pb } from "@/lib/pb";
import { CyberButton } from "../ui/cyber-button";
interface PBPricingPlan {
id: string;
name: string;
level: string;
tagline: string;
description: string;
price1m: number;
price6m: number;
price1y: number;
features: string;
popular: boolean;
cta: string;
lang: string;
order: number;
}
type Period = "1m" | "6m" | "1y";
interface PeriodOption {
id: Period;
label: string;
badge?: string;
}
export function Pricing({
dict,
lang,
}: {
dict: Dictionary["pricing"];
lang: string;
}) {
const [period, setPeriod] = useState<Period>("1y");
const [plans, setPlans] = useState<PBPricingPlan[]>([]);
const [loading, setLoading] = useState(!!lang);
useEffect(() => {
if (!lang) return;
async function fetchPlans() {
try {
setLoading(true);
const records = await pb
.collection(`pricing_${lang}`)
.getFullList<PBPricingPlan>({
sort: "order",
requestKey: null,
});
setPlans(records);
} catch (error) {
console.error("Ошибка загрузки тарифов:", error);
} finally {
setLoading(false);
}
}
fetchPlans();
}, [lang]);
const currencySign = lang === "ru" ? "₽" : "$";
const periods: PeriodOption[] = [
{ id: "1m", label: lang === "ru" ? "1 месяц" : "1 month" },
{ id: "6m", label: lang === "ru" ? "6 месяцев" : "6 months" },
{
id: "1y",
label: lang === "ru" ? "1 год" : "1 year",
badge: dict.recommend,
},
];
return (
<section
id="pricing"
className="py-32 px-4 container mx-auto max-w-7xl relative min-h-[600px]"
>
<div className="absolute inset-0 bg-[radial-gradient(circle_at_bottom_left,_var(--tw-gradient-stops))] from-secondary/10 via-transparent to-transparent -z-10" />
<div className="text-center mb-20 flex flex-col items-center">
<Badge
variant="outline"
className="mb-4 border-primary/30 text-primary uppercase tracking-widest font-mono text-[10px]"
>
{dict.badge}
</Badge>
<h2 className="text-4xl md:text-5xl font-bold tracking-tighter mb-8">
{dict.title}
</h2>
<div className="inline-flex items-center justify-center p-1.5 bg-card/50 border border-border/50 rounded-2xl backdrop-blur-sm relative">
{periods.map((p: PeriodOption) => (
<button
key={p.id}
onClick={() => setPeriod(p.id)}
className={`relative flex flex-col items-center justify-center gap-0.5 px-6 min-w-[120px] h-14 text-sm font-bold rounded-xl transition-all duration-300 ${
period === p.id
? "bg-primary text-primary-foreground shadow-lg shadow-primary/25"
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
}`}
>
<span>{p.label}</span>
{p.badge && (
<span
className={`text-[9px] uppercase tracking-widest italic leading-none font-black ${
period === p.id
? "text-primary-foreground/80"
: "text-primary/70"
}`}
>
{p.badge}
</span>
)}
</button>
))}
</div>
</div>
{loading ? (
<div className="flex flex-col items-center justify-center py-20 text-primary opacity-50">
<Loader2 className="animate-spin mb-4" />
<span className="text-sm uppercase font-mono tracking-widest">
{dict.syncing}
</span>
</div>
) : plans.length === 0 ? (
<p className="text-center text-muted-foreground font-mono">
{dict.empty}
</p>
) : (
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 items-stretch">
{plans.map((plan) => {
const months = period === "1m" ? 1 : period === "6m" ? 6 : 12;
const totalPrice =
period === "1m"
? plan.price1m
: period === "6m"
? plan.price6m
: plan.price1y;
const baseMonthlyPrice = plan.price1m;
const monthlyEquivalent = Math.round(totalPrice / months);
const isDiscounted = monthlyEquivalent < baseMonthlyPrice;
const formatPrice = (price: number) =>
lang === "ru" ? price.toLocaleString("ru-RU") : price.toString();
return (
<Card
key={plan.id}
className={`relative flex flex-col h-full transition-all duration-300 ${
plan.popular
? "border-primary/50 shadow-[0_0_30px_-10px_rgba(var(--primary),0.3)] scale-[1.02] z-10"
: "border-border/50"
}`}
>
{plan.popular && (
<div className="absolute top-0 right-10 -translate-y-1/2">
<Badge className="bg-primary text-primary-foreground rounded-full px-4 py-1 uppercase text-[10px] font-black tracking-widest shadow-lg shadow-primary/30">
{dict.recommend}
</Badge>
</div>
)}
<CardHeader className="pb-0 flex-none">
<div className="text-[10px] font-bold text-primary uppercase tracking-[0.2em] mb-2 h-4">
{plan.level}
</div>
<CardTitle className="text-3xl font-black text-foreground uppercase tracking-tight h-10 flex items-center">
{plan.name}
</CardTitle>
<div className="text-sm font-medium italic text-muted-foreground mt-4 border-l-2 border-primary/30 pl-3 min-h-[40px] flex items-center">
«{plan.tagline}»
</div>
<CardDescription className="mt-4 text-sm leading-relaxed min-h-[60px]">
{plan.description}
</CardDescription>
<div className="mt-6 mb-4 flex flex-col min-h-[110px] justify-center">
<div className="flex items-center gap-2 h-6 mb-1">
{isDiscounted && (
<>
<span className="text-sm text-muted-foreground/50 line-through decoration-primary/50 font-mono">
{lang === "ru" ? "" : currencySign}
{formatPrice(baseMonthlyPrice)}
{lang === "ru" ? currencySign : ""}
</span>
<Badge
variant="outline"
className="bg-primary/10 text-primary border-primary/30 text-[10px] px-2 py-0.5 font-bold tracking-wider"
>
{dict.discount}{" "}
{Math.round(
(1 - monthlyEquivalent / baseMonthlyPrice) * 100,
)}
%
</Badge>
</>
)}
</div>
<div className="flex items-baseline gap-1">
<span
className={`text-5xl font-black tracking-tighter font-mono ${plan.popular ? "text-foreground drop-shadow-[0_0_15px_rgba(var(--primary),0.3)]" : "text-foreground"}`}
>
{lang === "ru" ? "" : currencySign}
{formatPrice(monthlyEquivalent)}
</span>
{lang === "ru" && (
<span className="text-3xl font-bold ml-1 text-muted-foreground">
{currencySign}
</span>
)}
<span className="text-muted-foreground text-sm font-bold ml-2">
{dict.mo}
</span>
</div>
<div className="h-8 mt-4 flex items-center">
{period !== "1m" && (
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-md bg-muted/40 border border-border/50 text-[11px] text-muted-foreground font-mono uppercase tracking-wider">
<span>{dict.billedOnce}</span>
<span className="font-bold text-foreground">
{lang === "ru" ? "" : currencySign}
{formatPrice(totalPrice)}
{lang === "ru" ? currencySign : ""}
</span>
</div>
)}
</div>
</div>
</CardHeader>
<CardContent className="flex-1 pb-8">
<ul className="space-y-4 pt-6 border-t border-border/20">
{plan.features
.split("\n")
.filter((f) => f.trim())
.map((feature, i) => (
<li
key={i}
className="flex items-start gap-3 text-sm text-foreground/80 group"
>
<div className="bg-primary/10 w-5 h-5 rounded-full flex items-center justify-center mt-0.5 shrink-0 transition-colors group-hover:bg-primary/20">
<CheckIcon className="w-3 h-3 text-primary stroke-[3]" />
</div>
<span className="leading-tight font-medium">
{feature.trim()}
</span>
</li>
))}
</ul>
</CardContent>
<CardFooter className="pt-0">
<CyberButton
href="#"
variant={plan.popular ? "primary" : "secondary"}
className="w-full"
>
{plan.cta}
</CyberButton>
</CardFooter>
</Card>
);
})}
</div>
)}
</section>
);
}