127 lines
5.5 KiB
TypeScript
127 lines
5.5 KiB
TypeScript
"use client";
|
||
|
||
import type { CommercialBundle, CommercialPricingItem, CommercialProduct } from "../types";
|
||
import { Badge, Button } from "@/components/ui";
|
||
import { enumLabel, formatMoney, humanizeCommercialCode } from "../presentation";
|
||
|
||
export function BundleCard({
|
||
bundle,
|
||
onSelect,
|
||
selected,
|
||
recommendationScore,
|
||
pricingItems = [],
|
||
products = [],
|
||
}: {
|
||
bundle: CommercialBundle;
|
||
onSelect?: () => void;
|
||
selected?: boolean;
|
||
recommendationScore?: number;
|
||
pricingItems?: CommercialPricingItem[];
|
||
products?: CommercialProduct[];
|
||
}) {
|
||
const features = bundle.feature_refs?.length
|
||
? bundle.feature_refs
|
||
: bundle.capability_codes || [];
|
||
|
||
const prices = (bundle.pricing_refs || [])
|
||
.map((ref) => pricingItems.find((item) => item.pricing_item_code === ref))
|
||
.filter((item): item is CommercialPricingItem => Boolean(item));
|
||
const productNames = (bundle.product_codes || []).map(
|
||
(code) => products.find((product) => product.product_code === code)?.display_name || humanizeCommercialCode(code)
|
||
);
|
||
const badge = bundle.recommendation_badge
|
||
? enumLabel(bundle.recommendation_badge)
|
||
: recommendationScore != null
|
||
? "پیشنهاد ویژه برای شما"
|
||
: null;
|
||
const unresolvedPriceLabels = (bundle.pricing_refs || [])
|
||
.filter((ref) => !prices.some((item) => item.pricing_item_code === ref))
|
||
.map((ref) => {
|
||
if (/monthly/i.test(ref)) return "پرداخت ماهانه";
|
||
if (/yearly|annual/i.test(ref)) return "پرداخت سالانه";
|
||
if (/trial/i.test(ref)) return "آزمایش رایگان";
|
||
if (/enterprise|quote/i.test(ref)) return "استعلام قیمت سازمانی";
|
||
return "روش پرداخت قابل انتخاب";
|
||
});
|
||
const hasCoupon = bundle.discount_refs?.some((ref) => /coupon/i.test(ref));
|
||
|
||
return (
|
||
<div
|
||
className={`rounded-2xl border p-5 ${
|
||
selected
|
||
? "border-primary bg-[var(--color-primary-soft)] shadow-md"
|
||
: "border-gray-200 bg-white shadow-sm"
|
||
}`}
|
||
>
|
||
<div className="flex flex-wrap items-start justify-between gap-2">
|
||
<div>
|
||
<h3 className="text-lg font-bold text-secondary">{bundle.display_name}</h3>
|
||
{bundle.description ? (
|
||
<p className="mt-1 text-sm text-gray-600">{bundle.description}</p>
|
||
) : null}
|
||
</div>
|
||
<div className="flex flex-wrap gap-1">
|
||
{badge ? (
|
||
<Badge variant="primary">{badge}</Badge>
|
||
) : null}
|
||
{bundle.trial_eligible ? <Badge variant="default">آزمایش رایگان</Badge> : null}
|
||
{typeof recommendationScore === "number" ? (
|
||
<Badge variant="default">متناسب با نیاز شما</Badge>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
{productNames.length ? (
|
||
<div className="mt-4">
|
||
<p className="text-xs font-semibold text-gray-500">محصولات این بسته</p>
|
||
<div className="mt-2 flex flex-wrap gap-2">
|
||
{productNames.map((name) => (
|
||
<span key={name} className="rounded-full bg-sky-50 px-3 py-1 text-xs text-sky-800">
|
||
{name}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
{features.length ? (
|
||
<div className="mt-4">
|
||
<p className="text-xs font-semibold text-gray-500">امکانات کلیدی</p>
|
||
<div className="mt-2 flex flex-wrap gap-2">
|
||
{features.slice(0, 12).map((feature) => (
|
||
<span key={feature} className="rounded-full bg-emerald-50 px-3 py-1 text-xs text-emerald-800">
|
||
✓ {humanizeCommercialCode(feature)}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
{prices.length ? (
|
||
<div className="mt-4 grid gap-2 sm:grid-cols-2">
|
||
{prices.map((price) => (
|
||
<div key={price.pricing_item_code} className="rounded-xl bg-gray-50 p-3">
|
||
<p className="text-xs text-gray-500">{enumLabel(price.pricing_model)}</p>
|
||
<p className="mt-1 font-bold text-secondary">{formatMoney(price.amount_minor, price.currency)}</p>
|
||
{price.trial_days ? <p className="mt-1 text-xs text-primary">{price.trial_days} روز آزمایش رایگان</p> : null}
|
||
{price.tax_class_code ? <p className="mt-1 text-[11px] text-gray-500">مالیات مطابق قوانین محاسبه میشود</p> : null}
|
||
</div>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
{unresolvedPriceLabels.length ? <div className="mt-4 flex flex-wrap gap-2">{Array.from(new Set(unresolvedPriceLabels)).map((label) => <span key={label} className="rounded-full bg-gray-100 px-3 py-1 text-xs text-gray-700">{label}</span>)}</div> : null}
|
||
{bundle.discount_refs?.length ? (
|
||
<div className="mt-3 flex flex-wrap gap-2 text-xs font-medium text-emerald-700">
|
||
<span className="rounded-full bg-emerald-50 px-3 py-1">تخفیف ویژه فعال</span>
|
||
{hasCoupon ? <span className="rounded-full bg-emerald-50 px-3 py-1">کوپن قابل استفاده</span> : null}
|
||
</div>
|
||
) : null}
|
||
{bundle.upgrade_path_code ? (
|
||
<p className="mt-1 text-xs text-primary">امکان ارتقا در هر زمان</p>
|
||
) : null}
|
||
{onSelect ? (
|
||
<Button className="mt-4 w-full" size="sm" variant={selected ? "primary" : "outline"} onClick={onSelect}>
|
||
{selected ? "این بسته انتخاب شد" : "انتخاب این بسته"}
|
||
</Button>
|
||
) : null}
|
||
</div>
|
||
);
|
||
}
|