Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b2f15151d | |||
| 7d06f149d3 |
@@ -107,7 +107,8 @@ public record PublicMenuDto(
|
||||
string CafeName,
|
||||
string Slug,
|
||||
CafeThemeDto Theme,
|
||||
IReadOnlyList<PublicMenuCategoryDto> Categories);
|
||||
IReadOnlyList<PublicMenuCategoryDto> Categories,
|
||||
bool ShowWatermark);
|
||||
|
||||
public record GuestCreateOrderRequest(
|
||||
OrderType OrderType,
|
||||
|
||||
@@ -53,6 +53,7 @@ public class PublicService : IPublicService
|
||||
private readonly IBranchIdentityService _identity;
|
||||
private readonly IAbuseProtectionService _abuse;
|
||||
private readonly IHttpContextAccessor _http;
|
||||
private readonly Meezi.Infrastructure.Services.Platform.IPlatformCatalogService _catalog;
|
||||
|
||||
public PublicService(
|
||||
AppDbContext db,
|
||||
@@ -62,7 +63,8 @@ public class PublicService : IPublicService
|
||||
IBranchMenuService branchMenu,
|
||||
IBranchIdentityService identity,
|
||||
IAbuseProtectionService abuse,
|
||||
IHttpContextAccessor http)
|
||||
IHttpContextAccessor http,
|
||||
Meezi.Infrastructure.Services.Platform.IPlatformCatalogService catalog)
|
||||
{
|
||||
_db = db;
|
||||
_orders = orders;
|
||||
@@ -72,8 +74,13 @@ public class PublicService : IPublicService
|
||||
_identity = identity;
|
||||
_abuse = abuse;
|
||||
_http = http;
|
||||
_catalog = catalog;
|
||||
}
|
||||
|
||||
/// <summary>Free menus show a Meezi watermark; the `watermark_removed` feature (paid) hides it.</summary>
|
||||
private async Task<bool> ShowWatermarkAsync(Cafe cafe, CancellationToken ct) =>
|
||||
!await _catalog.IsFeatureEnabledForCafeAsync(cafe.Id, cafe.PlanTier, "watermark_removed", ct);
|
||||
|
||||
public Task<IReadOnlyList<CafeDiscoverDto>> DiscoverAsync(
|
||||
DiscoverFilterParams filters,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
@@ -190,7 +197,8 @@ public class PublicService : IPublicService
|
||||
.Where(c => c.Items.Count > 0)
|
||||
.ToList();
|
||||
|
||||
return new PublicMenuDto(cafe.Id, cafe.Name, cafe.Slug, CafeThemeMapping.FromJson(cafe.ThemeJson), grouped);
|
||||
return new PublicMenuDto(cafe.Id, cafe.Name, cafe.Slug, CafeThemeMapping.FromJson(cafe.ThemeJson), grouped,
|
||||
await ShowWatermarkAsync(cafe, cancellationToken));
|
||||
}
|
||||
|
||||
public async Task<(GuestOrderPlacedDto? Data, string? ErrorCode, string? ErrorMessage)> PlaceOrderAsync(
|
||||
@@ -357,7 +365,8 @@ public class PublicService : IPublicService
|
||||
.OrderBy(c => categoryById.GetValueOrDefault(c.Id)?.SortOrder ?? 0)
|
||||
.ToList();
|
||||
|
||||
return new PublicMenuDto(cafe.Id, cafe.Name, cafe.Slug, CafeThemeMapping.FromJson(cafe.ThemeJson), grouped);
|
||||
return new PublicMenuDto(cafe.Id, cafe.Name, cafe.Slug, CafeThemeMapping.FromJson(cafe.ThemeJson), grouped,
|
||||
await ShowWatermarkAsync(cafe, cancellationToken));
|
||||
}
|
||||
|
||||
public async Task<(GuestQrOrderPlacedDto? Data, string? ErrorCode, string? Message)> PlaceBranchGuestOrderAsync(
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Meezi.Core.Enums;
|
||||
using Meezi.Core.Platform;
|
||||
using Meezi.Infrastructure.Services.Platform;
|
||||
|
||||
namespace Meezi.API.Tests;
|
||||
|
||||
/// <summary>Test double: every feature enabled, unlimited limits. Keeps plan gating
|
||||
/// out of the way for service-level tests.</summary>
|
||||
internal sealed class NoOpPlatformCatalogService : IPlatformCatalogService
|
||||
{
|
||||
public Task<IReadOnlyList<PlanDefinitionDto>> GetPlansAsync(CancellationToken ct = default) =>
|
||||
Task.FromResult<IReadOnlyList<PlanDefinitionDto>>([]);
|
||||
|
||||
public Task<PlanDefinitionDto?> GetPlanAsync(PlanTier tier, CancellationToken ct = default) =>
|
||||
Task.FromResult<PlanDefinitionDto?>(null);
|
||||
|
||||
public Task<PlanLimitsData> GetLimitsAsync(PlanTier tier, CancellationToken ct = default) =>
|
||||
Task.FromResult(new PlanLimitsData());
|
||||
|
||||
public Task<decimal> GetMonthlyPriceTomanAsync(PlanTier tier, CancellationToken ct = default) =>
|
||||
Task.FromResult(0m);
|
||||
|
||||
public Task<bool> IsBillableOnlineAsync(PlanTier tier, CancellationToken ct = default) =>
|
||||
Task.FromResult(false);
|
||||
|
||||
public Task<IReadOnlyList<PlatformSettingDto>> GetSettingsAsync(CancellationToken ct = default) =>
|
||||
Task.FromResult<IReadOnlyList<PlatformSettingDto>>([]);
|
||||
|
||||
public Task<string?> GetSettingAsync(string key, CancellationToken ct = default) =>
|
||||
Task.FromResult<string?>(null);
|
||||
|
||||
public Task<IReadOnlyList<PlatformFeatureDto>> GetFeaturesAsync(CancellationToken ct = default) =>
|
||||
Task.FromResult<IReadOnlyList<PlatformFeatureDto>>([]);
|
||||
|
||||
public Task<IReadOnlyDictionary<string, bool>> GetEffectiveFeaturesForCafeAsync(
|
||||
string cafeId, PlanTier planTier, CancellationToken ct = default) =>
|
||||
Task.FromResult<IReadOnlyDictionary<string, bool>>(new Dictionary<string, bool>());
|
||||
|
||||
public Task<bool> IsFeatureEnabledForCafeAsync(
|
||||
string cafeId, PlanTier planTier, string featureKey, CancellationToken ct = default) =>
|
||||
Task.FromResult(true);
|
||||
|
||||
public void InvalidateCache() { }
|
||||
}
|
||||
@@ -120,7 +120,7 @@ public class QrMenuTests
|
||||
var http = new HttpContextAccessor();
|
||||
var media = new NoOpMediaStorageService();
|
||||
var reviews = new ReviewService(db, abuse, http, media);
|
||||
var publicSvc = new PublicService(db, orders, reviews, kds, branchMenu, identity, abuse, http);
|
||||
var publicSvc = new PublicService(db, orders, reviews, kds, branchMenu, identity, abuse, http, new NoOpPlatformCatalogService());
|
||||
|
||||
return (db, tables, publicSvc, cafeId, branchId, tableId, itemA, itemB, qrCode);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ export function QrGuestMenu({ code }: QrGuestMenuProps) {
|
||||
const [tableOrders, setTableOrders] = useState<GuestOrderRef[]>([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [menuTheme, setMenuTheme] = useState<CafeTheme | null>(null);
|
||||
const [showWatermark, setShowWatermark] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [view3dItem, setView3dItem] = useState<QrPublicMenuItem | null>(null);
|
||||
const [security, setSecurity] = useState<PublicSecurityConfig | null>(null);
|
||||
@@ -111,6 +112,7 @@ export function QrGuestMenu({ code }: QrGuestMenuProps) {
|
||||
const cats = menu.categories ?? [];
|
||||
setCategories(cats);
|
||||
setMenuTheme(normalizeCafeTheme(menu.theme ?? undefined));
|
||||
setShowWatermark(menu.showWatermark ?? false);
|
||||
setActiveCategory(QR_ALL_CATEGORY_ID);
|
||||
if (cats.length === 0) {
|
||||
setError(t("emptyMenu"));
|
||||
@@ -565,6 +567,16 @@ export function QrGuestMenu({ code }: QrGuestMenuProps) {
|
||||
view3d: t("view3d"),
|
||||
}}
|
||||
/>
|
||||
{showWatermark ? (
|
||||
<a
|
||||
href="https://meezi.ir"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-1 py-5 text-xs qr-muted opacity-70"
|
||||
>
|
||||
ساختهشده با <span className="font-bold">میزی</span>
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
{totalItems > 0 ? (
|
||||
<div className="pointer-events-none fixed inset-x-0 bottom-[3.25rem] z-40 mx-auto max-w-md px-3 pb-1">
|
||||
|
||||
@@ -53,6 +53,8 @@ export type QrPublicMenu = {
|
||||
slug: string;
|
||||
theme?: CafeTheme | null;
|
||||
categories: QrPublicMenuCategory[];
|
||||
/** Free plan shows the Meezi watermark under the menu; paid plans hide it. */
|
||||
showWatermark?: boolean;
|
||||
};
|
||||
|
||||
export type QrCartLine = {
|
||||
|
||||
@@ -73,7 +73,11 @@
|
||||
"inventory": "Inventory Management",
|
||||
"inventoryDesc": "Automatic ingredient tracking, low-stock alerts, and daily consumption reports.",
|
||||
"multiBranch": "Multi-Branch Management",
|
||||
"multiBranchDesc": "Manage and compare all your branches from a single central dashboard."
|
||||
"multiBranchDesc": "Manage and compare all your branches from a single central dashboard.",
|
||||
"offline": "Works offline",
|
||||
"offlineDesc": "Keep taking orders with no internet — everything syncs automatically when you reconnect.",
|
||||
"koja": "Get discovered on Koja",
|
||||
"kojaDesc": "Your cafe appears on the Koja discovery platform (koja.meezi.ir) to attract new customers."
|
||||
},
|
||||
"howItWorks": {
|
||||
"badge": "Easy Setup",
|
||||
@@ -121,50 +125,60 @@
|
||||
"yearlyDiscount": "2 months free",
|
||||
"popular": "Most popular",
|
||||
"freeName": "Free",
|
||||
"freePrice": "Free",
|
||||
"freePrice": "0",
|
||||
"freePriceNote": "forever",
|
||||
"freeDesc": "For small cafes just getting started.",
|
||||
"ctaFree": "Start for free",
|
||||
"f1": "1 branch",
|
||||
"f2": "Up to 50 orders/day",
|
||||
"f3": "QR digital menu",
|
||||
"f4": "Tables & reservations",
|
||||
"f5": "Basic dashboard",
|
||||
"freeDesc": "For small cafes getting started.",
|
||||
"ctaFree": "Start free",
|
||||
"f1": "Full QR digital menu",
|
||||
"f2": "Up to 6 tables",
|
||||
"f3": "30 orders/day",
|
||||
"f4": "Listed on Koja (koja.meezi.ir)",
|
||||
"f5": "Offline mode + waiter app",
|
||||
"proName": "Pro",
|
||||
"proPrice": "₺1,490,000",
|
||||
"proPrice": "1,490,000 Toman",
|
||||
"proPriceNote": "/ month",
|
||||
"proDesc": "For growing cafes that need professional features.",
|
||||
"proDesc": "For growing cafes.",
|
||||
"ctaPro": "Get Pro",
|
||||
"p1": "3 branches — unlimited orders",
|
||||
"p2": "3 POS terminals",
|
||||
"p3": "Full POS & kitchen KDS",
|
||||
"p1": "Everything in Starter",
|
||||
"p2": "3 branches & 3 terminals",
|
||||
"p3": "CRM / loyalty",
|
||||
"p4": "Full analytics & reports",
|
||||
"p5": "Tax system integration",
|
||||
"p6": "50 marketing SMS / month",
|
||||
"p7": "Phone support",
|
||||
"p5": "Tax system",
|
||||
"p6": "Delivery integration",
|
||||
"p7": "Payroll management",
|
||||
"businessName": "Business",
|
||||
"businessPrice": "₺3,490,000",
|
||||
"businessPrice": "3,490,000 Toman",
|
||||
"businessPriceNote": "/ month",
|
||||
"businessDesc": "For restaurants and multi-branch chains.",
|
||||
"businessDesc": "For restaurants & chains.",
|
||||
"ctaBusiness": "Get Business",
|
||||
"b1": "Unlimited branches — unlimited orders",
|
||||
"b2": "Unlimited terminals",
|
||||
"b3": "HR module & shift management",
|
||||
"b4": "Delivery platform integration",
|
||||
"b5": "200 marketing SMS / month",
|
||||
"b6": "Waiter mobile app",
|
||||
"b1": "Everything in Pro",
|
||||
"b2": "Unlimited branches",
|
||||
"b3": "Unlimited terminals",
|
||||
"b4": "3D menu",
|
||||
"b5": "AI 3D model generation",
|
||||
"b6": "Advanced delivery integration",
|
||||
"b7": "Priority support",
|
||||
"enterpriseName": "Enterprise",
|
||||
"enterprisePrice": "Contact us",
|
||||
"enterprisePriceNote": "custom pricing",
|
||||
"enterpriseDesc": "For large chains with specific needs.",
|
||||
"enterpriseDesc": "For large chains.",
|
||||
"ctaEnterprise": "Contact us",
|
||||
"e1": "Unlimited branches",
|
||||
"e1": "Everything in Business",
|
||||
"e2": "Public API",
|
||||
"e3": "White-label branding",
|
||||
"e4": "Trust badges",
|
||||
"e5": "Custom SLA",
|
||||
"e6": "24/7 support"
|
||||
"e4": "Custom SLA",
|
||||
"e5": "24/7 support",
|
||||
"e6": "Dedicated manager",
|
||||
"starterName": "Starter",
|
||||
"starterPrice": "690,000 Toman",
|
||||
"starterPriceNote": "/ month",
|
||||
"starterDesc": "Remove the watermark & customize.",
|
||||
"ctaStarter": "Get Starter",
|
||||
"s1": "Everything in Free",
|
||||
"s2": "Remove Meezi watermark",
|
||||
"s3": "Custom menu styling",
|
||||
"s4": "Reply to customer reviews",
|
||||
"s5": "Up to 15 tables"
|
||||
},
|
||||
"faq": {
|
||||
"badge": "FAQ",
|
||||
|
||||
@@ -73,7 +73,11 @@
|
||||
"inventory": "مدیریت موجودی",
|
||||
"inventoryDesc": "کنترل خودکار مواد اولیه، هشدار کمبود موجودی و گزارش مصرف روزانه.",
|
||||
"multiBranch": "مدیریت چند شعبه",
|
||||
"multiBranchDesc": "تمام شعبههایتان را از یک داشبورد مرکزی مدیریت و مقایسه کنید."
|
||||
"multiBranchDesc": "تمام شعبههایتان را از یک داشبورد مرکزی مدیریت و مقایسه کنید.",
|
||||
"offline": "کار بدون اینترنت",
|
||||
"offlineDesc": "حتی با قطع اینترنت، ثبت سفارش و کار ادامه دارد و هنگام اتصال همهچیز همگام میشود.",
|
||||
"koja": "نمایش در کجا",
|
||||
"kojaDesc": "کافه شما در پلتفرم کشف «کجا» (koja.meezi.ir) دیده میشود و مشتری جدید جذب میکنید."
|
||||
},
|
||||
"howItWorks": {
|
||||
"badge": "شروع آسان",
|
||||
@@ -113,58 +117,68 @@
|
||||
"t3Text": "با میزی میتوانم همه ۴ شعبهام را از یک جا مدیریت کنم. دیگر نیازی به گزارش جداگانه نیست."
|
||||
},
|
||||
"pricing": {
|
||||
"badge": "قیمتگذاری",
|
||||
"title": "برای هر مقیاسی یک پلن مناسب",
|
||||
"subtitle": "بدون هزینه پنهان — دقیقاً همان چیزی که میبینید پرداخت میکنید.",
|
||||
"badge": "تعرفهها",
|
||||
"title": "یک پلن برای هر مقیاس",
|
||||
"subtitle": "بدون هزینه پنهان؛ دقیقاً همان چیزی که میبینید پرداخت میکنید.",
|
||||
"monthly": "ماهانه",
|
||||
"yearly": "سالانه",
|
||||
"yearlyDiscount": "۲ ماه رایگان",
|
||||
"popular": "پرفروش",
|
||||
"popular": "محبوبترین",
|
||||
"freeName": "رایگان",
|
||||
"freePrice": "رایگان",
|
||||
"freePriceNote": "برای همیشه",
|
||||
"freeDesc": "برای کافههای کوچک که میخواهند شروع کنند.",
|
||||
"freePrice": "۰",
|
||||
"freePriceNote": "همیشه رایگان",
|
||||
"freeDesc": "برای شروع کافههای کوچک.",
|
||||
"ctaFree": "شروع رایگان",
|
||||
"f1": "۱ شعبه",
|
||||
"f2": "تا ۵۰ سفارش در روز",
|
||||
"f3": "منوی دیجیتال QR",
|
||||
"f4": "میز و رزرو",
|
||||
"f5": "داشبورد پایه",
|
||||
"proName": "پرو",
|
||||
"proPrice": "۱٬۴۹۰٬۰۰۰",
|
||||
"proPriceNote": "تومان / ماه",
|
||||
"proDesc": "برای کافههای در حال رشد با نیاز به امکانات حرفهای.",
|
||||
"ctaPro": "خرید پرو",
|
||||
"p1": "۳ شعبه — سفارش نامحدود",
|
||||
"p2": "۳ ترمینال صندوق",
|
||||
"p3": "POS کامل و آشپزخانه KDS",
|
||||
"p4": "گزارشهای کامل و تحلیلی",
|
||||
"p5": "سامانه مودیان (تاراز)",
|
||||
"p6": "۵۰ پیامک بازاریابی",
|
||||
"p7": "پشتیبانی تلفنی",
|
||||
"businessName": "بیزنس",
|
||||
"businessPrice": "۳٬۴۹۰٬۰۰۰",
|
||||
"businessPriceNote": "تومان / ماه",
|
||||
"businessDesc": "برای رستورانها و زنجیرههای چند شعبهای.",
|
||||
"ctaBusiness": "خرید بیزنس",
|
||||
"b1": "شعبه نامحدود — سفارش نامحدود",
|
||||
"b2": "ترمینال نامحدود",
|
||||
"b3": "ماژول منابع انسانی و شیفت",
|
||||
"b4": "یکپارچگی اسنپفود / پیک",
|
||||
"b5": "۲۰۰ پیامک بازاریابی",
|
||||
"b6": "اپ موبایل گارسون",
|
||||
"f1": "منوی دیجیتال QR کامل",
|
||||
"f2": "تا ۶ میز",
|
||||
"f3": "۳۰ سفارش در روز",
|
||||
"f4": "نمایش در کجا (koja.meezi.ir)",
|
||||
"f5": "حالت آفلاین + اپ گارسون",
|
||||
"proName": "حرفهای",
|
||||
"proPrice": "۱٬۴۹۰٬۰۰۰ تومان",
|
||||
"proPriceNote": "/ ماه",
|
||||
"proDesc": "برای کافههای در حال رشد.",
|
||||
"ctaPro": "انتخاب حرفهای",
|
||||
"p1": "همه امکانات پایه",
|
||||
"p2": "تا ۳ شعبه و ۳ پایانه",
|
||||
"p3": "باشگاه مشتریان (CRM)",
|
||||
"p4": "گزارشها و تحلیل کامل",
|
||||
"p5": "سیستم مالیات",
|
||||
"p6": "اتصال به پلتفرمهای پیک",
|
||||
"p7": "مدیریت حقوق و دستمزد",
|
||||
"businessName": "کسبوکار",
|
||||
"businessPrice": "۳٬۴۹۰٬۰۰۰ تومان",
|
||||
"businessPriceNote": "/ ماه",
|
||||
"businessDesc": "برای رستورانها و زنجیرهها.",
|
||||
"ctaBusiness": "انتخاب کسبوکار",
|
||||
"b1": "همه امکانات حرفهای",
|
||||
"b2": "شعب نامحدود",
|
||||
"b3": "پایانه نامحدود",
|
||||
"b4": "منوی سهبعدی",
|
||||
"b5": "ساخت ۳D با هوش مصنوعی",
|
||||
"b6": "اتصال پیک پیشرفته",
|
||||
"b7": "پشتیبانی اولویتدار",
|
||||
"enterpriseName": "سازمانی",
|
||||
"enterprisePrice": "تماس بگیرید",
|
||||
"enterprisePriceNote": "قیمت سفارشی",
|
||||
"enterpriseDesc": "برای زنجیرههای بزرگ با نیازهای خاص.",
|
||||
"enterprisePriceNote": "قیمت اختصاصی",
|
||||
"enterpriseDesc": "برای زنجیرههای بزرگ.",
|
||||
"ctaEnterprise": "تماس با ما",
|
||||
"e1": "شعبه نامحدود",
|
||||
"e1": "همه امکانات کسبوکار",
|
||||
"e2": "API عمومی",
|
||||
"e3": "برند اختصاصی (White-label)",
|
||||
"e4": "نشان اعتبار",
|
||||
"e5": "SLA اختصاصی",
|
||||
"e6": "پشتیبانی ۲۴/۷"
|
||||
"e4": "SLA اختصاصی",
|
||||
"e5": "پشتیبانی ۲۴/۷",
|
||||
"e6": "مدیر اختصاصی",
|
||||
"starterName": "پایه",
|
||||
"starterPrice": "۶۹۰٬۰۰۰ تومان",
|
||||
"starterPriceNote": "/ ماه",
|
||||
"starterDesc": "برای حذف واترمارک و شخصیسازی.",
|
||||
"ctaStarter": "شروع پایه",
|
||||
"s1": "همه امکانات رایگان",
|
||||
"s2": "حذف واترمارک میزی از منو",
|
||||
"s3": "طراحی اختصاصی منو",
|
||||
"s4": "پاسخ به نظرات مشتریان",
|
||||
"s5": "تا ۱۵ میز"
|
||||
},
|
||||
"faq": {
|
||||
"badge": "سوالات متداول",
|
||||
|
||||
@@ -6,10 +6,14 @@ import {
|
||||
Users,
|
||||
Package,
|
||||
Building2,
|
||||
WifiOff,
|
||||
MapPin,
|
||||
} from "lucide-react";
|
||||
|
||||
const FEATURES = [
|
||||
{ icon: QrCode, key: "qrMenu", descKey: "qrMenuDesc", color: "bg-brand-50 text-brand-700" },
|
||||
{ icon: WifiOff, key: "offline", descKey: "offlineDesc", color: "bg-emerald-50 text-emerald-700" },
|
||||
{ icon: MapPin, key: "koja", descKey: "kojaDesc", color: "bg-sky-50 text-sky-700" },
|
||||
{ icon: ShoppingCart, key: "pos", descKey: "posDesc", color: "bg-amber-50 text-amber-700" },
|
||||
{ icon: BarChart3, key: "analytics", descKey: "analyticsDesc", color: "bg-blue-50 text-blue-700" },
|
||||
{ icon: Users, key: "staff", descKey: "staffDesc", color: "bg-purple-50 text-purple-700" },
|
||||
|
||||
@@ -39,11 +39,25 @@ export function PricingSection() {
|
||||
popular: false,
|
||||
variant: "outline",
|
||||
},
|
||||
{
|
||||
id: "starter",
|
||||
name: t("starterName"),
|
||||
price: yearly
|
||||
? (locale === "fa" ? "۵۷۵٬۰۰۰ تومان" : "575,000 Toman")
|
||||
: t("starterPrice"),
|
||||
priceNote: t("starterPriceNote"),
|
||||
desc: t("starterDesc"),
|
||||
cta: t("ctaStarter"),
|
||||
href: `${base}/demo`,
|
||||
features: [t("s1"), t("s2"), t("s3"), t("s4"), t("s5")],
|
||||
popular: false,
|
||||
variant: "outline",
|
||||
},
|
||||
{
|
||||
id: "pro",
|
||||
name: t("proName"),
|
||||
price: yearly
|
||||
? (locale === "fa" ? "۱٬۲۴۲٬۰۰۰" : "₺1,242,000")
|
||||
? (locale === "fa" ? "۱٬۲۴۲٬۰۰۰ تومان" : "1,242,000 Toman")
|
||||
: t("proPrice"),
|
||||
priceNote: t("proPriceNote"),
|
||||
desc: t("proDesc"),
|
||||
@@ -57,7 +71,7 @@ export function PricingSection() {
|
||||
id: "business",
|
||||
name: t("businessName"),
|
||||
price: yearly
|
||||
? (locale === "fa" ? "۲٬۹۰۸٬۰۰۰" : "₺2,908,000")
|
||||
? (locale === "fa" ? "۲٬۹۰۸٬۰۰۰ تومان" : "2,908,000 Toman")
|
||||
: t("businessPrice"),
|
||||
priceNote: t("businessPriceNote"),
|
||||
desc: t("businessDesc"),
|
||||
@@ -120,8 +134,8 @@ export function PricingSection() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plan cards — 4-column grid on xl, 2-col on md, 1-col on mobile */}
|
||||
<div className="mt-12 grid gap-5 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{/* Plan cards — 5 tiers: 5-col on xl, 3-col on lg, 2-col on md, 1-col on mobile */}
|
||||
<div className="mt-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
|
||||
{plans.map((plan) => (
|
||||
<PlanCard key={plan.id} plan={plan} t={t} />
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user