TorbatYar/frontend/modules/hospitality/components/HospitalityBundleGate.tsx
2026-07-28 20:39:10 +03:30

97 lines
3.2 KiB
TypeScript

"use client";
import { useHospitalityCapabilities } from "@/modules/hospitality/hooks/useHospitalityCapabilities";
import { HospitalityPageLoader } from "@/modules/hospitality/pages/shared";
import { FeatureLock } from "@/modules/commercial/components";
import { useTenantId } from "@/hooks/useTenantId";
import { CapabilityUnavailable } from "@/modules/hospitality/components/CapabilityUnavailable";
/** Maps hospitality capability/feature keys → L2 bundle codes (service-owned). */
export const FEATURE_TO_BUNDLE: Record<string, string> = {
digital_menu: "digital_menu",
digital_menu_catalog: "digital_menu",
qr_menu: "qr_menu",
qr_ordering: "qr_ordering",
pos_lite: "pos_lite",
pos_pro: "pos_pro",
kitchen: "kitchen",
kitchen_engine: "kitchen",
reservation: "reservation",
table_service: "table_service",
delivery_integration: "delivery_connector",
delivery_connector: "delivery_connector",
accounting_integration: "accounting_connector",
accounting_connector: "accounting_connector",
crm_integration: "crm_connector",
crm_connector: "crm_connector",
loyalty_integration: "loyalty_connector",
loyalty_connector: "loyalty_connector",
communication_integration: "communication_connector",
communication_connector: "communication_connector",
website_integration: "website_connector",
website_connector: "website_connector",
analytics: "analytics",
};
/**
* Dual-layer gate:
* 1) Hospitality L2 `/capabilities` feature flag
* 2) Commercial entitlement (FeatureLock / UpgradePrompt)
*
* Never silently hides. Never uses fake unlock.
*/
export function HospitalityBundleGate({
feature,
capabilities,
children,
}: {
feature: string;
capabilities?: { features: Record<string, boolean> };
children: React.ReactNode;
}) {
const capsQ = useHospitalityCapabilities();
const { tenantId } = useTenantId();
const caps = capabilities ?? capsQ.data;
const bundle = FEATURE_TO_BUNDLE[feature] ?? feature;
const commercialKey = `hospitality.${bundle}`;
if (capsQ.isLoading && !capabilities) return <HospitalityPageLoader />;
const featureFlag =
caps?.features?.[feature] ?? caps?.features?.[bundle] ?? caps?.features?.[FEATURE_TO_BUNDLE[feature]];
if (featureFlag === false) {
return (
<CapabilityUnavailable
title="ماژول در بسته فعال نیست"
description="این بخش در بسته (bundle) فعال Hospitality شما موجود نیست."
requiredBundle={bundle}
requiredCapability={feature}
secondaryHref="/hospitality/bundles"
secondaryLabel="بسته‌های Hospitality"
/>
);
}
if (!tenantId) return <>{children}</>;
return (
<FeatureLock
featureKey={commercialKey}
tenantId={tenantId}
requiredBundle={bundle}
requiredCapability={commercialKey}
fallback={
<CapabilityUnavailable
title="نیاز به entitlement تجاری"
description="دسترسی تجاری برای این قابلیت تأیید نشد."
requiredBundle={bundle}
requiredCapability={commercialKey}
/>
}
>
{children}
</FeatureLock>
);
}