{plan.title}
-{plan.bundle?.description || plan.description}
++ {commercialDescription("bundles", plan.bundle?.description || plan.description)} +
-
- {capabilityNames.map((code) =>
- ✓{humanizeCommercialCode(code)} )} + {capabilityNames.map((code) =>
- ✓{commercialEntityName("capabilities", code)} )}
{bundle.display_name}
- {bundle.description ? ( -{bundle.description}
- ) : null} +{bundleName}
++ {commercialDescription("bundles", bundle.description)} +
{enumLabel(price.pricing_model)}
++ {commercialEntityName("pricing", price.pricing_item_code, price.display_name)} +
+{enumLabel(price.pricing_model)}
{formatMoney(price.amount_minor, price.currency)}
{price.trial_days ?{price.trial_days} روز آزمایش رایگان
: null} {price.tax_class_code ?مالیات مطابق قوانین محاسبه میشود
: null} diff --git a/frontend/modules/commercial/components/CommercialSidebar.tsx b/frontend/modules/commercial/components/CommercialSidebar.tsx index 01ca0c3..355aad2 100644 --- a/frontend/modules/commercial/components/CommercialSidebar.tsx +++ b/frontend/modules/commercial/components/CommercialSidebar.tsx @@ -4,8 +4,9 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { Button } from "@/components/ui"; import { loadCommercialProducts } from "../adapters"; -import type { CommercialProduct } from "../types"; -import { resolveProductLaunchUrl } from "../types"; +import type { CommercialProduct } from "../types"; +import { resolveProductLaunchUrl } from "../types"; +import { commercialEntityName } from "../presentation"; /** Dynamic sidebar — product launch URLs from registry metadata only. */ export function CommercialSidebar({ className = "" }: { className?: string }) { @@ -30,8 +31,8 @@ export function CommercialSidebar({ className = "" }: { className?: string }) { فضای کاری - - Products + + همه محصولات اشتراک و پرداخت @@ -47,7 +48,7 @@ export function CommercialSidebar({ className = "" }: { className?: string }) { className="rounded-lg px-2 py-1.5 text-sm text-secondary hover:bg-gray-50" > {p.icon ? {p.icon} : null} - {p.display_name} + {commercialEntityName("products", p.product_code, p.display_name)} ); }) diff --git a/frontend/modules/commercial/components/PlatformShell.tsx b/frontend/modules/commercial/components/PlatformShell.tsx index 79d6b8d..ea7313f 100644 --- a/frontend/modules/commercial/components/PlatformShell.tsx +++ b/frontend/modules/commercial/components/PlatformShell.tsx @@ -8,8 +8,9 @@ import { Button } from "@/components/ui"; import { useAuth } from "@/hooks/useAuth"; import { useMe } from "@/hooks/useMe"; import { loadCommercialProducts } from "../adapters"; -import type { CommercialProduct } from "../types"; -import { resolveProductLaunchUrl } from "../types"; +import type { CommercialProduct } from "../types"; +import { resolveProductLaunchUrl } from "../types"; +import { commercialEntityName } from "../presentation"; const CORE_NAV = [ { href: "/dashboard", label: "داشبورد" }, @@ -46,7 +47,7 @@ export function PlatformShell({ }); }, []); - const filtered = products.filter((p) => { + const filtered = products.filter((p) => { if (!query.trim()) return true; const q = query.trim().toLowerCase(); return ( @@ -54,7 +55,10 @@ export function PlatformShell({ p.product_code.toLowerCase().includes(q) || (p.category || "").toLowerCase().includes(q) ); - }); + }); + + const productName = (product: CommercialProduct) => + commercialEntityName("products", product.product_code, product.display_name); const navLink = (href: string, label: string) => { const active = pathname === href || pathname.startsWith(`${href}/`); @@ -106,11 +110,11 @@ export function PlatformShell({ className="rounded-xl px-3 py-2 text-sm text-gray-400" title="این محصول هنوز آماده راهاندازی نیست" > - {p.display_name} + {productName(p)} ); } - return navLink(href, p.display_name); + return navLink(href, productName(p)); }) ) : (محصولی کشف نشد
diff --git a/frontend/modules/commercial/components/ProductCard.tsx b/frontend/modules/commercial/components/ProductCard.tsx index ca37294..85dc5de 100644 --- a/frontend/modules/commercial/components/ProductCard.tsx +++ b/frontend/modules/commercial/components/ProductCard.tsx @@ -6,7 +6,11 @@ import { resolveProductLaunchUrl } from "../types"; import { Button } from "@/components/ui"; import { FeatureLock } from "./FeatureLock"; import { pushRecent } from "../launcher-prefs"; -import { enumLabel } from "../presentation"; +import { + commercialDescription, + commercialEntityName, + enumLabel, +} from "../presentation"; export function ProductCard({ product, @@ -29,7 +33,12 @@ export function ProductCard({ const launchUrl = resolveProductLaunchUrl(product); const strategy = product.launch_strategy || "route"; const requiredCap = product.required_capabilities?.[0]; - const setupHref = `/setup/${encodeURIComponent(product.product_code)}`; + const setupHref = `/setup/${encodeURIComponent(product.product_code)}`; + const productName = commercialEntityName( + "products", + product.product_code, + product.display_name + ); const markRecent = () => { pushRecent(product.product_code); @@ -65,11 +74,11 @@ export function ProductCard({ className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl text-lg font-bold text-white" style={{ background: product.color || "var(--color-primary)" }} > - {product.icon || (product.display_name?.charAt(0) ?? "P")} + {product.icon || productName.charAt(0)}{product.display_name}
+{productName}
{product.category ? ( {enumLabel(product.category)} @@ -79,9 +88,9 @@ export function ProductCard({ پیشنهاد مناسب شما ) : null}{product.description}
- ) : null} ++ {commercialDescription("products", product.description)} +
{product.trial_eligible ?امکان استفاده آزمایشی
: null}مشاهده جزئیات
++ {objectTitle || "مشاهده جزئیات"} +
-
{entries.map(([key, nested]) => (
- {fieldLabel(key)} -
{title}
+}) { + const presentation = getRegistryPresentation(kind); + const sectionTitle = title || presentation.sectionTitle; + if (!result || result.availability === "loading") { + return ( +{sectionTitle}
{availabilityLabel("loading")}
{title}
+{sectionTitle}
{subtitle ?{subtitle}
: null}{title}
+{sectionTitle}
{subtitle ?{subtitle}
: null}-
{result.data.map((raw, idx) => {
const item = (raw && typeof raw === "object" ? raw : {}) as Record
- {fieldLabel(key)} -
- {fieldLabel(key)}
-
-
+ - - پلن مناسب: {humanizeCommercialCode(requiredPlan)} + پلن مناسب: {commercialEntityName("plans", requiredPlan)} ) : null} {requiredCapability ? (
- قابلیت لازم:{" "} - {humanizeCommercialCode(requiredCapability)} + {commercialEntityName("capabilities", requiredCapability)} ) : null} {requiredBundle ? (
- بسته مناسب:{" "} - {humanizeCommercialCode(requiredBundle)} + {commercialEntityName("bundles", requiredBundle)} ) : null}
-
{requiredPlan ? (
{r.label}
-{r.description}
- - ))} +{r.label}
+{r.description}
+{r.label}
-مدیریت و پیگیری {r.label}
- - ))} + href={`/admin/commercial/${r.key}`} + className={`${cardClass} transition-shadow hover:shadow-md`} + > +{r.presentation.navigationTitle}
+{r.presentation.description}
+{group.title}
این بخش برای تنظیمات کماستفاده است. تغییر آنها ممکن است روی انتشار اثر بگذارد.