Compare commits
2 Commits
2487f9e30f
...
9b2f15151d
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b2f15151d | |||
| 7d06f149d3 |
@@ -107,7 +107,8 @@ public record PublicMenuDto(
|
|||||||
string CafeName,
|
string CafeName,
|
||||||
string Slug,
|
string Slug,
|
||||||
CafeThemeDto Theme,
|
CafeThemeDto Theme,
|
||||||
IReadOnlyList<PublicMenuCategoryDto> Categories);
|
IReadOnlyList<PublicMenuCategoryDto> Categories,
|
||||||
|
bool ShowWatermark);
|
||||||
|
|
||||||
public record GuestCreateOrderRequest(
|
public record GuestCreateOrderRequest(
|
||||||
OrderType OrderType,
|
OrderType OrderType,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public class PublicService : IPublicService
|
|||||||
private readonly IBranchIdentityService _identity;
|
private readonly IBranchIdentityService _identity;
|
||||||
private readonly IAbuseProtectionService _abuse;
|
private readonly IAbuseProtectionService _abuse;
|
||||||
private readonly IHttpContextAccessor _http;
|
private readonly IHttpContextAccessor _http;
|
||||||
|
private readonly Meezi.Infrastructure.Services.Platform.IPlatformCatalogService _catalog;
|
||||||
|
|
||||||
public PublicService(
|
public PublicService(
|
||||||
AppDbContext db,
|
AppDbContext db,
|
||||||
@@ -62,7 +63,8 @@ public class PublicService : IPublicService
|
|||||||
IBranchMenuService branchMenu,
|
IBranchMenuService branchMenu,
|
||||||
IBranchIdentityService identity,
|
IBranchIdentityService identity,
|
||||||
IAbuseProtectionService abuse,
|
IAbuseProtectionService abuse,
|
||||||
IHttpContextAccessor http)
|
IHttpContextAccessor http,
|
||||||
|
Meezi.Infrastructure.Services.Platform.IPlatformCatalogService catalog)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_orders = orders;
|
_orders = orders;
|
||||||
@@ -72,8 +74,13 @@ public class PublicService : IPublicService
|
|||||||
_identity = identity;
|
_identity = identity;
|
||||||
_abuse = abuse;
|
_abuse = abuse;
|
||||||
_http = http;
|
_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(
|
public Task<IReadOnlyList<CafeDiscoverDto>> DiscoverAsync(
|
||||||
DiscoverFilterParams filters,
|
DiscoverFilterParams filters,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
@@ -190,7 +197,8 @@ public class PublicService : IPublicService
|
|||||||
.Where(c => c.Items.Count > 0)
|
.Where(c => c.Items.Count > 0)
|
||||||
.ToList();
|
.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(
|
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)
|
.OrderBy(c => categoryById.GetValueOrDefault(c.Id)?.SortOrder ?? 0)
|
||||||
.ToList();
|
.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(
|
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 http = new HttpContextAccessor();
|
||||||
var media = new NoOpMediaStorageService();
|
var media = new NoOpMediaStorageService();
|
||||||
var reviews = new ReviewService(db, abuse, http, media);
|
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);
|
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 [tableOrders, setTableOrders] = useState<GuestOrderRef[]>([]);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [menuTheme, setMenuTheme] = useState<CafeTheme | null>(null);
|
const [menuTheme, setMenuTheme] = useState<CafeTheme | null>(null);
|
||||||
|
const [showWatermark, setShowWatermark] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [view3dItem, setView3dItem] = useState<QrPublicMenuItem | null>(null);
|
const [view3dItem, setView3dItem] = useState<QrPublicMenuItem | null>(null);
|
||||||
const [security, setSecurity] = useState<PublicSecurityConfig | null>(null);
|
const [security, setSecurity] = useState<PublicSecurityConfig | null>(null);
|
||||||
@@ -111,6 +112,7 @@ export function QrGuestMenu({ code }: QrGuestMenuProps) {
|
|||||||
const cats = menu.categories ?? [];
|
const cats = menu.categories ?? [];
|
||||||
setCategories(cats);
|
setCategories(cats);
|
||||||
setMenuTheme(normalizeCafeTheme(menu.theme ?? undefined));
|
setMenuTheme(normalizeCafeTheme(menu.theme ?? undefined));
|
||||||
|
setShowWatermark(menu.showWatermark ?? false);
|
||||||
setActiveCategory(QR_ALL_CATEGORY_ID);
|
setActiveCategory(QR_ALL_CATEGORY_ID);
|
||||||
if (cats.length === 0) {
|
if (cats.length === 0) {
|
||||||
setError(t("emptyMenu"));
|
setError(t("emptyMenu"));
|
||||||
@@ -565,6 +567,16 @@ export function QrGuestMenu({ code }: QrGuestMenuProps) {
|
|||||||
view3d: t("view3d"),
|
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>
|
</div>
|
||||||
{totalItems > 0 ? (
|
{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">
|
<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;
|
slug: string;
|
||||||
theme?: CafeTheme | null;
|
theme?: CafeTheme | null;
|
||||||
categories: QrPublicMenuCategory[];
|
categories: QrPublicMenuCategory[];
|
||||||
|
/** Free plan shows the Meezi watermark under the menu; paid plans hide it. */
|
||||||
|
showWatermark?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QrCartLine = {
|
export type QrCartLine = {
|
||||||
|
|||||||
@@ -73,7 +73,11 @@
|
|||||||
"inventory": "Inventory Management",
|
"inventory": "Inventory Management",
|
||||||
"inventoryDesc": "Automatic ingredient tracking, low-stock alerts, and daily consumption reports.",
|
"inventoryDesc": "Automatic ingredient tracking, low-stock alerts, and daily consumption reports.",
|
||||||
"multiBranch": "Multi-Branch Management",
|
"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": {
|
"howItWorks": {
|
||||||
"badge": "Easy Setup",
|
"badge": "Easy Setup",
|
||||||
@@ -121,50 +125,60 @@
|
|||||||
"yearlyDiscount": "2 months free",
|
"yearlyDiscount": "2 months free",
|
||||||
"popular": "Most popular",
|
"popular": "Most popular",
|
||||||
"freeName": "Free",
|
"freeName": "Free",
|
||||||
"freePrice": "Free",
|
"freePrice": "0",
|
||||||
"freePriceNote": "forever",
|
"freePriceNote": "forever",
|
||||||
"freeDesc": "For small cafes just getting started.",
|
"freeDesc": "For small cafes getting started.",
|
||||||
"ctaFree": "Start for free",
|
"ctaFree": "Start free",
|
||||||
"f1": "1 branch",
|
"f1": "Full QR digital menu",
|
||||||
"f2": "Up to 50 orders/day",
|
"f2": "Up to 6 tables",
|
||||||
"f3": "QR digital menu",
|
"f3": "30 orders/day",
|
||||||
"f4": "Tables & reservations",
|
"f4": "Listed on Koja (koja.meezi.ir)",
|
||||||
"f5": "Basic dashboard",
|
"f5": "Offline mode + waiter app",
|
||||||
"proName": "Pro",
|
"proName": "Pro",
|
||||||
"proPrice": "₺1,490,000",
|
"proPrice": "1,490,000 Toman",
|
||||||
"proPriceNote": "/ month",
|
"proPriceNote": "/ month",
|
||||||
"proDesc": "For growing cafes that need professional features.",
|
"proDesc": "For growing cafes.",
|
||||||
"ctaPro": "Get Pro",
|
"ctaPro": "Get Pro",
|
||||||
"p1": "3 branches — unlimited orders",
|
"p1": "Everything in Starter",
|
||||||
"p2": "3 POS terminals",
|
"p2": "3 branches & 3 terminals",
|
||||||
"p3": "Full POS & kitchen KDS",
|
"p3": "CRM / loyalty",
|
||||||
"p4": "Full analytics & reports",
|
"p4": "Full analytics & reports",
|
||||||
"p5": "Tax system integration",
|
"p5": "Tax system",
|
||||||
"p6": "50 marketing SMS / month",
|
"p6": "Delivery integration",
|
||||||
"p7": "Phone support",
|
"p7": "Payroll management",
|
||||||
"businessName": "Business",
|
"businessName": "Business",
|
||||||
"businessPrice": "₺3,490,000",
|
"businessPrice": "3,490,000 Toman",
|
||||||
"businessPriceNote": "/ month",
|
"businessPriceNote": "/ month",
|
||||||
"businessDesc": "For restaurants and multi-branch chains.",
|
"businessDesc": "For restaurants & chains.",
|
||||||
"ctaBusiness": "Get Business",
|
"ctaBusiness": "Get Business",
|
||||||
"b1": "Unlimited branches — unlimited orders",
|
"b1": "Everything in Pro",
|
||||||
"b2": "Unlimited terminals",
|
"b2": "Unlimited branches",
|
||||||
"b3": "HR module & shift management",
|
"b3": "Unlimited terminals",
|
||||||
"b4": "Delivery platform integration",
|
"b4": "3D menu",
|
||||||
"b5": "200 marketing SMS / month",
|
"b5": "AI 3D model generation",
|
||||||
"b6": "Waiter mobile app",
|
"b6": "Advanced delivery integration",
|
||||||
"b7": "Priority support",
|
"b7": "Priority support",
|
||||||
"enterpriseName": "Enterprise",
|
"enterpriseName": "Enterprise",
|
||||||
"enterprisePrice": "Contact us",
|
"enterprisePrice": "Contact us",
|
||||||
"enterprisePriceNote": "custom pricing",
|
"enterprisePriceNote": "custom pricing",
|
||||||
"enterpriseDesc": "For large chains with specific needs.",
|
"enterpriseDesc": "For large chains.",
|
||||||
"ctaEnterprise": "Contact us",
|
"ctaEnterprise": "Contact us",
|
||||||
"e1": "Unlimited branches",
|
"e1": "Everything in Business",
|
||||||
"e2": "Public API",
|
"e2": "Public API",
|
||||||
"e3": "White-label branding",
|
"e3": "White-label branding",
|
||||||
"e4": "Trust badges",
|
"e4": "Custom SLA",
|
||||||
"e5": "Custom SLA",
|
"e5": "24/7 support",
|
||||||
"e6": "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": {
|
"faq": {
|
||||||
"badge": "FAQ",
|
"badge": "FAQ",
|
||||||
|
|||||||
@@ -73,7 +73,11 @@
|
|||||||
"inventory": "مدیریت موجودی",
|
"inventory": "مدیریت موجودی",
|
||||||
"inventoryDesc": "کنترل خودکار مواد اولیه، هشدار کمبود موجودی و گزارش مصرف روزانه.",
|
"inventoryDesc": "کنترل خودکار مواد اولیه، هشدار کمبود موجودی و گزارش مصرف روزانه.",
|
||||||
"multiBranch": "مدیریت چند شعبه",
|
"multiBranch": "مدیریت چند شعبه",
|
||||||
"multiBranchDesc": "تمام شعبههایتان را از یک داشبورد مرکزی مدیریت و مقایسه کنید."
|
"multiBranchDesc": "تمام شعبههایتان را از یک داشبورد مرکزی مدیریت و مقایسه کنید.",
|
||||||
|
"offline": "کار بدون اینترنت",
|
||||||
|
"offlineDesc": "حتی با قطع اینترنت، ثبت سفارش و کار ادامه دارد و هنگام اتصال همهچیز همگام میشود.",
|
||||||
|
"koja": "نمایش در کجا",
|
||||||
|
"kojaDesc": "کافه شما در پلتفرم کشف «کجا» (koja.meezi.ir) دیده میشود و مشتری جدید جذب میکنید."
|
||||||
},
|
},
|
||||||
"howItWorks": {
|
"howItWorks": {
|
||||||
"badge": "شروع آسان",
|
"badge": "شروع آسان",
|
||||||
@@ -113,58 +117,68 @@
|
|||||||
"t3Text": "با میزی میتوانم همه ۴ شعبهام را از یک جا مدیریت کنم. دیگر نیازی به گزارش جداگانه نیست."
|
"t3Text": "با میزی میتوانم همه ۴ شعبهام را از یک جا مدیریت کنم. دیگر نیازی به گزارش جداگانه نیست."
|
||||||
},
|
},
|
||||||
"pricing": {
|
"pricing": {
|
||||||
"badge": "قیمتگذاری",
|
"badge": "تعرفهها",
|
||||||
"title": "برای هر مقیاسی یک پلن مناسب",
|
"title": "یک پلن برای هر مقیاس",
|
||||||
"subtitle": "بدون هزینه پنهان — دقیقاً همان چیزی که میبینید پرداخت میکنید.",
|
"subtitle": "بدون هزینه پنهان؛ دقیقاً همان چیزی که میبینید پرداخت میکنید.",
|
||||||
"monthly": "ماهانه",
|
"monthly": "ماهانه",
|
||||||
"yearly": "سالانه",
|
"yearly": "سالانه",
|
||||||
"yearlyDiscount": "۲ ماه رایگان",
|
"yearlyDiscount": "۲ ماه رایگان",
|
||||||
"popular": "پرفروش",
|
"popular": "محبوبترین",
|
||||||
"freeName": "رایگان",
|
"freeName": "رایگان",
|
||||||
"freePrice": "رایگان",
|
"freePrice": "۰",
|
||||||
"freePriceNote": "برای همیشه",
|
"freePriceNote": "همیشه رایگان",
|
||||||
"freeDesc": "برای کافههای کوچک که میخواهند شروع کنند.",
|
"freeDesc": "برای شروع کافههای کوچک.",
|
||||||
"ctaFree": "شروع رایگان",
|
"ctaFree": "شروع رایگان",
|
||||||
"f1": "۱ شعبه",
|
"f1": "منوی دیجیتال QR کامل",
|
||||||
"f2": "تا ۵۰ سفارش در روز",
|
"f2": "تا ۶ میز",
|
||||||
"f3": "منوی دیجیتال QR",
|
"f3": "۳۰ سفارش در روز",
|
||||||
"f4": "میز و رزرو",
|
"f4": "نمایش در کجا (koja.meezi.ir)",
|
||||||
"f5": "داشبورد پایه",
|
"f5": "حالت آفلاین + اپ گارسون",
|
||||||
"proName": "پرو",
|
"proName": "حرفهای",
|
||||||
"proPrice": "۱٬۴۹۰٬۰۰۰",
|
"proPrice": "۱٬۴۹۰٬۰۰۰ تومان",
|
||||||
"proPriceNote": "تومان / ماه",
|
"proPriceNote": "/ ماه",
|
||||||
"proDesc": "برای کافههای در حال رشد با نیاز به امکانات حرفهای.",
|
"proDesc": "برای کافههای در حال رشد.",
|
||||||
"ctaPro": "خرید پرو",
|
"ctaPro": "انتخاب حرفهای",
|
||||||
"p1": "۳ شعبه — سفارش نامحدود",
|
"p1": "همه امکانات پایه",
|
||||||
"p2": "۳ ترمینال صندوق",
|
"p2": "تا ۳ شعبه و ۳ پایانه",
|
||||||
"p3": "POS کامل و آشپزخانه KDS",
|
"p3": "باشگاه مشتریان (CRM)",
|
||||||
"p4": "گزارشهای کامل و تحلیلی",
|
"p4": "گزارشها و تحلیل کامل",
|
||||||
"p5": "سامانه مودیان (تاراز)",
|
"p5": "سیستم مالیات",
|
||||||
"p6": "۵۰ پیامک بازاریابی",
|
"p6": "اتصال به پلتفرمهای پیک",
|
||||||
"p7": "پشتیبانی تلفنی",
|
"p7": "مدیریت حقوق و دستمزد",
|
||||||
"businessName": "بیزنس",
|
"businessName": "کسبوکار",
|
||||||
"businessPrice": "۳٬۴۹۰٬۰۰۰",
|
"businessPrice": "۳٬۴۹۰٬۰۰۰ تومان",
|
||||||
"businessPriceNote": "تومان / ماه",
|
"businessPriceNote": "/ ماه",
|
||||||
"businessDesc": "برای رستورانها و زنجیرههای چند شعبهای.",
|
"businessDesc": "برای رستورانها و زنجیرهها.",
|
||||||
"ctaBusiness": "خرید بیزنس",
|
"ctaBusiness": "انتخاب کسبوکار",
|
||||||
"b1": "شعبه نامحدود — سفارش نامحدود",
|
"b1": "همه امکانات حرفهای",
|
||||||
"b2": "ترمینال نامحدود",
|
"b2": "شعب نامحدود",
|
||||||
"b3": "ماژول منابع انسانی و شیفت",
|
"b3": "پایانه نامحدود",
|
||||||
"b4": "یکپارچگی اسنپفود / پیک",
|
"b4": "منوی سهبعدی",
|
||||||
"b5": "۲۰۰ پیامک بازاریابی",
|
"b5": "ساخت ۳D با هوش مصنوعی",
|
||||||
"b6": "اپ موبایل گارسون",
|
"b6": "اتصال پیک پیشرفته",
|
||||||
"b7": "پشتیبانی اولویتدار",
|
"b7": "پشتیبانی اولویتدار",
|
||||||
"enterpriseName": "سازمانی",
|
"enterpriseName": "سازمانی",
|
||||||
"enterprisePrice": "تماس بگیرید",
|
"enterprisePrice": "تماس بگیرید",
|
||||||
"enterprisePriceNote": "قیمت سفارشی",
|
"enterprisePriceNote": "قیمت اختصاصی",
|
||||||
"enterpriseDesc": "برای زنجیرههای بزرگ با نیازهای خاص.",
|
"enterpriseDesc": "برای زنجیرههای بزرگ.",
|
||||||
"ctaEnterprise": "تماس با ما",
|
"ctaEnterprise": "تماس با ما",
|
||||||
"e1": "شعبه نامحدود",
|
"e1": "همه امکانات کسبوکار",
|
||||||
"e2": "API عمومی",
|
"e2": "API عمومی",
|
||||||
"e3": "برند اختصاصی (White-label)",
|
"e3": "برند اختصاصی (White-label)",
|
||||||
"e4": "نشان اعتبار",
|
"e4": "SLA اختصاصی",
|
||||||
"e5": "SLA اختصاصی",
|
"e5": "پشتیبانی ۲۴/۷",
|
||||||
"e6": "پشتیبانی ۲۴/۷"
|
"e6": "مدیر اختصاصی",
|
||||||
|
"starterName": "پایه",
|
||||||
|
"starterPrice": "۶۹۰٬۰۰۰ تومان",
|
||||||
|
"starterPriceNote": "/ ماه",
|
||||||
|
"starterDesc": "برای حذف واترمارک و شخصیسازی.",
|
||||||
|
"ctaStarter": "شروع پایه",
|
||||||
|
"s1": "همه امکانات رایگان",
|
||||||
|
"s2": "حذف واترمارک میزی از منو",
|
||||||
|
"s3": "طراحی اختصاصی منو",
|
||||||
|
"s4": "پاسخ به نظرات مشتریان",
|
||||||
|
"s5": "تا ۱۵ میز"
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"badge": "سوالات متداول",
|
"badge": "سوالات متداول",
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
Package,
|
Package,
|
||||||
Building2,
|
Building2,
|
||||||
|
WifiOff,
|
||||||
|
MapPin,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
const FEATURES = [
|
const FEATURES = [
|
||||||
{ icon: QrCode, key: "qrMenu", descKey: "qrMenuDesc", color: "bg-brand-50 text-brand-700" },
|
{ 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: 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: 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" },
|
{ icon: Users, key: "staff", descKey: "staffDesc", color: "bg-purple-50 text-purple-700" },
|
||||||
|
|||||||
@@ -39,11 +39,25 @@ export function PricingSection() {
|
|||||||
popular: false,
|
popular: false,
|
||||||
variant: "outline",
|
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",
|
id: "pro",
|
||||||
name: t("proName"),
|
name: t("proName"),
|
||||||
price: yearly
|
price: yearly
|
||||||
? (locale === "fa" ? "۱٬۲۴۲٬۰۰۰" : "₺1,242,000")
|
? (locale === "fa" ? "۱٬۲۴۲٬۰۰۰ تومان" : "1,242,000 Toman")
|
||||||
: t("proPrice"),
|
: t("proPrice"),
|
||||||
priceNote: t("proPriceNote"),
|
priceNote: t("proPriceNote"),
|
||||||
desc: t("proDesc"),
|
desc: t("proDesc"),
|
||||||
@@ -57,7 +71,7 @@ export function PricingSection() {
|
|||||||
id: "business",
|
id: "business",
|
||||||
name: t("businessName"),
|
name: t("businessName"),
|
||||||
price: yearly
|
price: yearly
|
||||||
? (locale === "fa" ? "۲٬۹۰۸٬۰۰۰" : "₺2,908,000")
|
? (locale === "fa" ? "۲٬۹۰۸٬۰۰۰ تومان" : "2,908,000 Toman")
|
||||||
: t("businessPrice"),
|
: t("businessPrice"),
|
||||||
priceNote: t("businessPriceNote"),
|
priceNote: t("businessPriceNote"),
|
||||||
desc: t("businessDesc"),
|
desc: t("businessDesc"),
|
||||||
@@ -120,8 +134,8 @@ export function PricingSection() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Plan cards — 4-column grid on xl, 2-col on md, 1-col on mobile */}
|
{/* 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-5 sm:grid-cols-2 xl:grid-cols-4">
|
<div className="mt-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
|
||||||
{plans.map((plan) => (
|
{plans.map((plan) => (
|
||||||
<PlanCard key={plan.id} plan={plan} t={t} />
|
<PlanCard key={plan.id} plan={plan} t={t} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user