Add TenantCustomTier system and fix BusinessEditModal feature loading
Backend: - Add TenantCustomTier model for per-tenant feature overrides - Update EntitlementService to check custom tier before plan features - Add custom_tier action on TenantViewSet (GET/PUT/DELETE) - Add Celery task for grace period management (30-day expiry) Frontend: - Add DynamicFeaturesEditor component for dynamic feature management - Fix BusinessEditModal to load features from plan defaults when no custom tier - Update limits (max_users, max_resources, etc.) to use featureValues - Remove outdated canonical feature check from FeaturePicker (removes warning icons) - Add useBillingPlans hook for accessing billing system data - Add custom tier API functions to platform.ts Features now follow consistent rules: - Load from plan defaults when no custom tier exists - Load from custom tier when one exists - Reset to plan defaults when plan changes - Save to custom tier on edit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -98,7 +98,7 @@ const PluginMarketplace: React.FC = () => {
|
||||
const hasPluginsFeature = canUse('plugins');
|
||||
const isLocked = !hasPluginsFeature;
|
||||
|
||||
// Fetch marketplace plugins
|
||||
// Fetch marketplace plugins - only when user has the feature
|
||||
const { data: plugins = [], isLoading, error } = useQuery<PluginTemplate[]>({
|
||||
queryKey: ['plugin-templates', 'marketplace'],
|
||||
queryFn: async () => {
|
||||
@@ -121,6 +121,8 @@ const PluginMarketplace: React.FC = () => {
|
||||
pluginCode: p.plugin_code,
|
||||
}));
|
||||
},
|
||||
// Don't fetch if user doesn't have the plugins feature
|
||||
enabled: hasPluginsFeature && !permissionsLoading,
|
||||
});
|
||||
|
||||
// Fetch installed plugins to check which are already installed
|
||||
@@ -130,6 +132,8 @@ const PluginMarketplace: React.FC = () => {
|
||||
const { data } = await api.get('/plugin-installations/');
|
||||
return data;
|
||||
},
|
||||
// Don't fetch if user doesn't have the plugins feature
|
||||
enabled: hasPluginsFeature && !permissionsLoading,
|
||||
});
|
||||
|
||||
// Create a set of installed template IDs for quick lookup
|
||||
@@ -223,7 +227,10 @@ const PluginMarketplace: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
// Check if error is a 403 (plan restriction) - show upgrade prompt instead
|
||||
const is403Error = error && (error as any)?.response?.status === 403;
|
||||
|
||||
if (error && !is403Error) {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
||||
@@ -235,8 +242,11 @@ const PluginMarketplace: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// If 403 error, treat as locked
|
||||
const effectivelyLocked = isLocked || is403Error;
|
||||
|
||||
return (
|
||||
<LockedSection feature="plugins" isLocked={isLocked} variant="overlay">
|
||||
<LockedSection feature="plugins" isLocked={effectivelyLocked} variant="overlay">
|
||||
<div className="p-8 space-y-6 max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
Reference in New Issue
Block a user