"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"; import { cn } from "@/lib/utils"; 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; } /** * Секция тарифов. Тарифные планы приходят из PocketBase (`pricing_{lang}`) с ценами * сразу на три периода (price1m/price6m/price1y) — компонент на клиенте пересчитывает * "цену в месяц" для выбранного периода и подсвечивает скидку, если помесячная цена * при длинном периоде ниже базовой месячной ставки. */ export function Pricing({ dict, lang, pbUrl, }: { dict: Dictionary["pricing"]; lang: string; /** * Адрес PocketBase — приходит пропсом из серверного page.tsx, а не читается * напрямую из process.env.NEXT_PUBLIC_* здесь (см. комментарий в * app/[lang]/auth/auth-client.tsx для полного объяснения). */ pbUrl: string; }) { const [period, setPeriod] = useState("1y"); const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(!!lang); useEffect(() => { if (!lang) return; async function fetchPlans() { try { setLoading(true); pb.baseURL = pbUrl; const records = await pb .collection(`pricing_${lang}`) .getFullList({ sort: "order", requestKey: null, }); setPlans(records); } catch (error) { console.error("Ошибка загрузки тарифов:", error); } finally { setLoading(false); } } fetchPlans(); }, [lang, pbUrl]); 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 (
{dict.badge}

{dict.title}

{periods.map((p: PeriodOption) => ( ))}
{loading ? (
{dict.syncing}
) : plans.length === 0 ? (

{dict.empty}

) : (
{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 ( {plan.popular && (
{dict.recommend}
)}
{plan.level}
{plan.name}
«{plan.tagline}»
{plan.description}
{isDiscounted && ( <> {lang === "ru" ? "" : currencySign} {formatPrice(baseMonthlyPrice)} {lang === "ru" ? currencySign : ""} {dict.discount}{" "} {Math.round( (1 - monthlyEquivalent / baseMonthlyPrice) * 100, )} % )}
{lang === "ru" ? "" : currencySign} {formatPrice(monthlyEquivalent)} {lang === "ru" && ( {currencySign} )} {dict.mo}
{period !== "1m" && (
{dict.billedOnce} {lang === "ru" ? "" : currencySign} {formatPrice(totalPrice)} {lang === "ru" ? currencySign : ""}
)}
    {plan.features .split("\n") .filter((f) => f.trim()) .map((feature, i) => (
  • {feature.trim()}
  • ))}
{plan.cta}
); })}
)}
); }