mirror of https://github.com/kortix-ai/suna.git
Add project limits based on subscription tier
Co-authored-by: sharath <sharath@kortix.ai>
This commit is contained in:
parent
ffb97b8dc8
commit
d395ac1a82
|
@ -1018,6 +1018,12 @@ async def initiate_agent_with_files(
|
|||
if not can_run:
|
||||
raise HTTPException(status_code=402, detail={"message": message, "subscription": subscription})
|
||||
|
||||
# Check project limits before creating a new project
|
||||
from services.billing import check_project_limits
|
||||
can_create_project, project_message, project_subscription = await check_project_limits(client, account_id)
|
||||
if not can_create_project:
|
||||
raise HTTPException(status_code=402, detail={"message": project_message, "subscription": project_subscription})
|
||||
|
||||
try:
|
||||
# 1. Create Project
|
||||
placeholder_name = f"{prompt[:30]}..." if len(prompt) > 30 else prompt
|
||||
|
|
|
@ -43,22 +43,22 @@ def get_model_pricing(model: str) -> tuple[float, float] | None:
|
|||
|
||||
|
||||
SUBSCRIPTION_TIERS = {
|
||||
config.STRIPE_FREE_TIER_ID: {'name': 'free', 'minutes': 60, 'cost': 5},
|
||||
config.STRIPE_TIER_2_20_ID: {'name': 'tier_2_20', 'minutes': 120, 'cost': 20 + 5}, # 2 hours
|
||||
config.STRIPE_TIER_6_50_ID: {'name': 'tier_6_50', 'minutes': 360, 'cost': 50 + 5}, # 6 hours
|
||||
config.STRIPE_TIER_12_100_ID: {'name': 'tier_12_100', 'minutes': 720, 'cost': 100 + 5}, # 12 hours
|
||||
config.STRIPE_TIER_25_200_ID: {'name': 'tier_25_200', 'minutes': 1500, 'cost': 200 + 5}, # 25 hours
|
||||
config.STRIPE_TIER_50_400_ID: {'name': 'tier_50_400', 'minutes': 3000, 'cost': 400 + 5}, # 50 hours
|
||||
config.STRIPE_TIER_125_800_ID: {'name': 'tier_125_800', 'minutes': 7500, 'cost': 800 + 5}, # 125 hours
|
||||
config.STRIPE_TIER_200_1000_ID: {'name': 'tier_200_1000', 'minutes': 12000, 'cost': 1000 + 5}, # 200 hours
|
||||
config.STRIPE_FREE_TIER_ID: {'name': 'free', 'minutes': 60, 'cost': 5, 'project_limit': 3},
|
||||
config.STRIPE_TIER_2_20_ID: {'name': 'tier_2_20', 'minutes': 120, 'cost': 20 + 5, 'project_limit': 10}, # 2 hours
|
||||
config.STRIPE_TIER_6_50_ID: {'name': 'tier_6_50', 'minutes': 360, 'cost': 50 + 5, 'project_limit': 25}, # 6 hours
|
||||
config.STRIPE_TIER_12_100_ID: {'name': 'tier_12_100', 'minutes': 720, 'cost': 100 + 5, 'project_limit': 50}, # 12 hours
|
||||
config.STRIPE_TIER_25_200_ID: {'name': 'tier_25_200', 'minutes': 1500, 'cost': 200 + 5, 'project_limit': 100}, # 25 hours
|
||||
config.STRIPE_TIER_50_400_ID: {'name': 'tier_50_400', 'minutes': 3000, 'cost': 400 + 5, 'project_limit': 200}, # 50 hours
|
||||
config.STRIPE_TIER_125_800_ID: {'name': 'tier_125_800', 'minutes': 7500, 'cost': 800 + 5, 'project_limit': 500}, # 125 hours
|
||||
config.STRIPE_TIER_200_1000_ID: {'name': 'tier_200_1000', 'minutes': 12000, 'cost': 1000 + 5, 'project_limit': 1000}, # 200 hours
|
||||
# Yearly tiers (same usage limits, different billing period)
|
||||
config.STRIPE_TIER_2_20_YEARLY_ID: {'name': 'tier_2_20', 'minutes': 120, 'cost': 20 + 5}, # 2 hours/month, $204/year
|
||||
config.STRIPE_TIER_6_50_YEARLY_ID: {'name': 'tier_6_50', 'minutes': 360, 'cost': 50 + 5}, # 6 hours/month, $510/year
|
||||
config.STRIPE_TIER_12_100_YEARLY_ID: {'name': 'tier_12_100', 'minutes': 720, 'cost': 100 + 5}, # 12 hours/month, $1020/year
|
||||
config.STRIPE_TIER_25_200_YEARLY_ID: {'name': 'tier_25_200', 'minutes': 1500, 'cost': 200 + 5}, # 25 hours/month, $2040/year
|
||||
config.STRIPE_TIER_50_400_YEARLY_ID: {'name': 'tier_50_400', 'minutes': 3000, 'cost': 400 + 5}, # 50 hours/month, $4080/year
|
||||
config.STRIPE_TIER_125_800_YEARLY_ID: {'name': 'tier_125_800', 'minutes': 7500, 'cost': 800 + 5}, # 125 hours/month, $8160/year
|
||||
config.STRIPE_TIER_200_1000_YEARLY_ID: {'name': 'tier_200_1000', 'minutes': 12000, 'cost': 1000 + 5}, # 200 hours/month, $10200/year
|
||||
config.STRIPE_TIER_2_20_YEARLY_ID: {'name': 'tier_2_20', 'minutes': 120, 'cost': 20 + 5, 'project_limit': 10}, # 2 hours/month, $204/year
|
||||
config.STRIPE_TIER_6_50_YEARLY_ID: {'name': 'tier_6_50', 'minutes': 360, 'cost': 50 + 5, 'project_limit': 25}, # 6 hours/month, $510/year
|
||||
config.STRIPE_TIER_12_100_YEARLY_ID: {'name': 'tier_12_100', 'minutes': 720, 'cost': 100 + 5, 'project_limit': 50}, # 12 hours/month, $1020/year
|
||||
config.STRIPE_TIER_25_200_YEARLY_ID: {'name': 'tier_25_200', 'minutes': 1500, 'cost': 200 + 5, 'project_limit': 100}, # 25 hours/month, $2040/year
|
||||
config.STRIPE_TIER_50_400_YEARLY_ID: {'name': 'tier_50_400', 'minutes': 3000, 'cost': 400 + 5, 'project_limit': 200}, # 50 hours/month, $4080/year
|
||||
config.STRIPE_TIER_125_800_YEARLY_ID: {'name': 'tier_125_800', 'minutes': 7500, 'cost': 800 + 5, 'project_limit': 500}, # 125 hours/month, $8160/year
|
||||
config.STRIPE_TIER_200_1000_YEARLY_ID: {'name': 'tier_200_1000', 'minutes': 12000, 'cost': 1000 + 5, 'project_limit': 1000}, # 200 hours/month, $10200/year
|
||||
}
|
||||
|
||||
# Pydantic models for request/response validation
|
||||
|
@ -81,6 +81,8 @@ class SubscriptionStatus(BaseModel):
|
|||
minutes_limit: Optional[int] = None
|
||||
cost_limit: Optional[float] = None
|
||||
current_usage: Optional[float] = None
|
||||
project_limit: Optional[int] = None # Added project limit
|
||||
current_project_count: Optional[int] = None # Added current project count
|
||||
# Fields for scheduled changes
|
||||
has_schedule: bool = False
|
||||
scheduled_plan_name: Optional[str] = None
|
||||
|
@ -503,6 +505,63 @@ async def check_billing_status(client, user_id: str) -> Tuple[bool, str, Optiona
|
|||
|
||||
return True, "OK", subscription
|
||||
|
||||
async def check_project_limits(client, user_id: str) -> Tuple[bool, str, Optional[Dict]]:
|
||||
"""
|
||||
Check if a user can create a new project based on their subscription and current project count.
|
||||
|
||||
Returns:
|
||||
Tuple[bool, str, Optional[Dict]]: (can_create, message, subscription_info)
|
||||
"""
|
||||
if config.ENV_MODE == EnvMode.LOCAL:
|
||||
logger.info("Running in local development mode - project limit checks are disabled")
|
||||
return True, "Local development mode - project limits disabled", {
|
||||
"price_id": "local_dev",
|
||||
"plan_name": "Local Development",
|
||||
"project_limit": "no limit"
|
||||
}
|
||||
|
||||
# Get current subscription
|
||||
subscription = await get_user_subscription(user_id)
|
||||
|
||||
# If no subscription, they can use free tier
|
||||
if not subscription:
|
||||
subscription = {
|
||||
'price_id': config.STRIPE_FREE_TIER_ID, # Free tier
|
||||
'plan_name': 'free'
|
||||
}
|
||||
|
||||
# Extract price ID from subscription items
|
||||
price_id = None
|
||||
if subscription.get('items') and subscription['items'].get('data') and len(subscription['items']['data']) > 0:
|
||||
price_id = subscription['items']['data'][0]['price']['id']
|
||||
else:
|
||||
price_id = subscription.get('price_id', config.STRIPE_FREE_TIER_ID)
|
||||
|
||||
# Get tier info - default to free tier if not found
|
||||
tier_info = SUBSCRIPTION_TIERS.get(price_id)
|
||||
if not tier_info:
|
||||
logger.warning(f"Unknown subscription tier: {price_id}, defaulting to free tier")
|
||||
tier_info = SUBSCRIPTION_TIERS[config.STRIPE_FREE_TIER_ID]
|
||||
|
||||
# Get current project count for the user
|
||||
project_count_result = await client.table('projects').select('project_id', count='exact').eq('account_id', user_id).execute()
|
||||
current_project_count = project_count_result.count or 0
|
||||
|
||||
# Check if within project limits
|
||||
project_limit = tier_info.get('project_limit', 3) # Default to 3 for free tier
|
||||
if current_project_count >= project_limit:
|
||||
return False, f"Project limit of {project_limit} reached. You currently have {current_project_count} projects. Please upgrade your plan to create more projects.", {
|
||||
**subscription,
|
||||
'project_limit': project_limit,
|
||||
'current_project_count': current_project_count
|
||||
}
|
||||
|
||||
return True, "OK", {
|
||||
**subscription,
|
||||
'project_limit': project_limit,
|
||||
'current_project_count': current_project_count
|
||||
}
|
||||
|
||||
# API endpoints
|
||||
@router.post("/create-checkout-session")
|
||||
async def create_checkout_session(
|
||||
|
@ -909,13 +968,20 @@ async def get_subscription(
|
|||
# Default to free tier status if no active subscription for our product
|
||||
free_tier_id = config.STRIPE_FREE_TIER_ID
|
||||
free_tier_info = SUBSCRIPTION_TIERS.get(free_tier_id)
|
||||
|
||||
# Get current project count for the user
|
||||
project_count_result = await client.table('projects').select('project_id', count='exact').eq('account_id', current_user_id).execute()
|
||||
current_project_count = project_count_result.count or 0
|
||||
|
||||
return SubscriptionStatus(
|
||||
status="no_subscription",
|
||||
plan_name=free_tier_info.get('name', 'free') if free_tier_info else 'free',
|
||||
price_id=free_tier_id,
|
||||
minutes_limit=free_tier_info.get('minutes') if free_tier_info else 0,
|
||||
cost_limit=free_tier_info.get('cost') if free_tier_info else 0,
|
||||
current_usage=current_usage
|
||||
current_usage=current_usage,
|
||||
project_limit=free_tier_info.get('project_limit', 3) if free_tier_info else 3,
|
||||
current_project_count=current_project_count
|
||||
)
|
||||
|
||||
# Extract current plan details
|
||||
|
@ -927,6 +993,10 @@ async def get_subscription(
|
|||
logger.warning(f"User {current_user_id} subscribed to unknown price {current_price_id}. Defaulting info.")
|
||||
current_tier_info = {'name': 'unknown', 'minutes': 0}
|
||||
|
||||
# Get current project count for the user
|
||||
project_count_result = await client.table('projects').select('project_id', count='exact').eq('account_id', current_user_id).execute()
|
||||
current_project_count = project_count_result.count or 0
|
||||
|
||||
status_response = SubscriptionStatus(
|
||||
status=subscription['status'], # 'active', 'trialing', etc.
|
||||
plan_name=subscription['plan'].get('nickname') or current_tier_info['name'],
|
||||
|
@ -937,6 +1007,8 @@ async def get_subscription(
|
|||
minutes_limit=current_tier_info['minutes'],
|
||||
cost_limit=current_tier_info['cost'],
|
||||
current_usage=current_usage,
|
||||
project_limit=current_tier_info.get('project_limit', 3),
|
||||
current_project_count=current_project_count,
|
||||
has_schedule=False # Default
|
||||
)
|
||||
|
||||
|
@ -998,6 +1070,31 @@ async def check_status(
|
|||
logger.error(f"Error checking billing status: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get("/check-project-limits")
|
||||
async def check_project_limits_endpoint(
|
||||
current_user_id: str = Depends(get_current_user_id_from_jwt)
|
||||
):
|
||||
"""Check if the user can create a new project based on their subscription and current project count."""
|
||||
try:
|
||||
db = DBConnection()
|
||||
client = await db.client
|
||||
|
||||
can_create, message, subscription = await check_project_limits(client, current_user_id)
|
||||
|
||||
return {
|
||||
"can_create": can_create,
|
||||
"message": message,
|
||||
"subscription": {
|
||||
"price_id": subscription.get('price_id', 'unknown'),
|
||||
"plan_name": subscription.get('plan_name', 'unknown'),
|
||||
"project_limit": subscription.get('project_limit', 'unknown'),
|
||||
"current_project_count": subscription.get('current_project_count', 0)
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking project limits: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
@router.post("/webhook")
|
||||
async def stripe_webhook(request: Request):
|
||||
"""Handle Stripe webhook events."""
|
||||
|
|
|
@ -65,6 +65,12 @@ class SessionManager:
|
|||
thread_id = str(uuid.uuid4())
|
||||
account_id = agent_config.get('account_id')
|
||||
|
||||
# Check project limits before creating a new project
|
||||
from services.billing import check_project_limits
|
||||
can_create_project, project_message, project_subscription = await check_project_limits(client, account_id)
|
||||
if not can_create_project:
|
||||
raise Exception(f"Project limit exceeded: {project_message}")
|
||||
|
||||
placeholder_name = f"Trigger: {agent_config.get('name', 'Agent')} - {trigger_event.trigger_id[:8]}"
|
||||
await client.table('projects').insert({
|
||||
"project_id": project_id,
|
||||
|
@ -96,6 +102,12 @@ class SessionManager:
|
|||
project_id = str(uuid.uuid4())
|
||||
thread_id = str(uuid.uuid4())
|
||||
|
||||
# Check project limits before creating a new project
|
||||
from services.billing import check_project_limits
|
||||
can_create_project, project_message, project_subscription = await check_project_limits(client, account_id)
|
||||
if not can_create_project:
|
||||
raise Exception(f"Project limit exceeded: {project_message}")
|
||||
|
||||
await client.table('projects').insert({
|
||||
"project_id": project_id,
|
||||
"account_id": account_id,
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
# Project Limits Feature
|
||||
|
||||
This document describes the project limits feature that restricts the number of projects users can create based on their subscription tier.
|
||||
|
||||
## Overview
|
||||
|
||||
The project limits feature ensures that users on free accounts have a reasonable limit on the number of projects they can create, while paid accounts have higher limits. This helps manage system resources and encourages users to upgrade for more projects.
|
||||
|
||||
## Subscription Tiers and Project Limits
|
||||
|
||||
| Subscription Tier | Project Limit | Description |
|
||||
|-------------------|---------------|-------------|
|
||||
| Free | 3 projects | Basic tier for new users |
|
||||
| Tier 2 ($20/month) | 10 projects | Entry-level paid tier |
|
||||
| Tier 6 ($50/month) | 25 projects | Mid-tier plan |
|
||||
| Tier 12 ($100/month) | 50 projects | Professional tier |
|
||||
| Tier 25 ($200/month) | 100 projects | Business tier |
|
||||
| Tier 50 ($400/month) | 200 projects | Enterprise tier |
|
||||
| Tier 125 ($800/month) | 500 projects | Large enterprise |
|
||||
| Tier 200 ($1000/month) | 1000 projects | Maximum tier |
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Backend Changes
|
||||
|
||||
1. **Billing Service (`backend/services/billing.py`)**:
|
||||
- Added `project_limit` field to `SUBSCRIPTION_TIERS` configuration
|
||||
- Created `check_project_limits()` function to validate project creation
|
||||
- Added `/billing/check-project-limits` API endpoint
|
||||
- Updated subscription status to include project limit information
|
||||
|
||||
2. **Agent API (`backend/agent/api.py`)**:
|
||||
- Added project limit check before creating projects in agent initiation
|
||||
- Returns HTTP 402 error when limits are exceeded
|
||||
|
||||
3. **Execution Service (`backend/triggers/services/execution_service.py`)**:
|
||||
- Added project limit checks in `create_agent_session()` and `create_workflow_session()`
|
||||
- Prevents automatic project creation when limits are reached
|
||||
|
||||
### Frontend Changes
|
||||
|
||||
1. **API Layer (`frontend/src/lib/api.ts`)**:
|
||||
- Added `checkProjectLimits()` function
|
||||
- Added `ProjectLimitsResponse` interface
|
||||
- Updated `SubscriptionStatus` interface to include project limits
|
||||
|
||||
2. **Project Mutations (`frontend/src/hooks/react-query/sidebar/use-project-mutations.ts`)**:
|
||||
- Added project limit check before creating projects
|
||||
- Shows error toast when limits are exceeded
|
||||
|
||||
3. **Billing Status Component (`frontend/src/components/billing/account-billing-status.tsx`)**:
|
||||
- Added project count display in billing status
|
||||
- Shows current projects vs. limit
|
||||
|
||||
4. **React Query Hook (`frontend/src/hooks/react-query/billing/use-project-limits.ts`)**:
|
||||
- Created hook for checking project limits
|
||||
- Caches results for 5 minutes
|
||||
|
||||
## Error Handling
|
||||
|
||||
When project limits are exceeded:
|
||||
|
||||
1. **Frontend**: Shows error toast with upgrade message
|
||||
2. **Backend**: Returns HTTP 402 (Payment Required) with detailed error message
|
||||
3. **API Response**: Includes current project count and limit information
|
||||
|
||||
## Testing
|
||||
|
||||
To test the feature:
|
||||
|
||||
1. Create projects up to your tier's limit
|
||||
2. Attempt to create an additional project
|
||||
3. Verify that appropriate error messages are shown
|
||||
4. Check that billing status displays current project count
|
||||
|
||||
## Local Development
|
||||
|
||||
In local development mode (`ENV_MODE=LOCAL`), project limits are disabled to allow unrestricted development and testing.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements to consider:
|
||||
|
||||
1. **Soft Limits**: Allow exceeding limits with warnings
|
||||
2. **Project Archiving**: Allow archiving old projects to free up slots
|
||||
3. **Team Limits**: Separate limits for team accounts
|
||||
4. **Usage Analytics**: Track project creation patterns
|
||||
5. **Dynamic Limits**: Adjust limits based on usage patterns
|
|
@ -105,7 +105,7 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
|||
|
||||
{subscriptionData ? (
|
||||
<>
|
||||
<div className="mb-6">
|
||||
<div className="mb-6 space-y-3">
|
||||
<div className="rounded-lg border bg-background p-4">
|
||||
<div className="flex justify-between items-center gap-4">
|
||||
<span className="text-sm font-medium text-foreground/90">
|
||||
|
@ -122,6 +122,18 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border bg-background p-4">
|
||||
<div className="flex justify-between items-center gap-4">
|
||||
<span className="text-sm font-medium text-foreground/90">
|
||||
Projects
|
||||
</span>
|
||||
<span className="text-sm font-medium text-card-title">
|
||||
{subscriptionData.current_project_count || 0} /{' '}
|
||||
{subscriptionData.project_limit || '∞'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plans Comparison */}
|
||||
|
@ -149,8 +161,8 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-6">
|
||||
<div className="rounded-lg border bg-background p-4 gap-4">
|
||||
<div className="mb-6 space-y-3">
|
||||
<div className="rounded-lg border bg-background p-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-foreground/90">
|
||||
Current Plan
|
||||
|
@ -159,7 +171,9 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
|||
Free
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border bg-background p-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-foreground/90">
|
||||
Agent Usage This Month
|
||||
|
@ -170,6 +184,18 @@ export default function AccountBillingStatus({ accountId, returnUrl }: Props) {
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border bg-background p-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-foreground/90">
|
||||
Projects
|
||||
</span>
|
||||
<span className="text-sm font-medium text-card-title">
|
||||
{subscriptionData?.current_project_count || 0} /{' '}
|
||||
{subscriptionData?.project_limit || '3'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plans Comparison */}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export { useProjectLimits } from './use-project-limits';
|
|
@ -0,0 +1,16 @@
|
|||
'use client';
|
||||
|
||||
import { createQueryHook } from '@/hooks/use-query';
|
||||
import { checkProjectLimits } from '@/lib/api';
|
||||
|
||||
export const useProjectLimits = createQueryHook(
|
||||
() => checkProjectLimits(),
|
||||
{
|
||||
queryKey: ['project-limits'],
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
errorContext: {
|
||||
operation: 'check project limits',
|
||||
resource: 'project limits'
|
||||
}
|
||||
}
|
||||
);
|
|
@ -5,14 +5,30 @@ import {
|
|||
createProject,
|
||||
updateProject,
|
||||
deleteProject,
|
||||
Project
|
||||
Project,
|
||||
checkProjectLimits
|
||||
} from '@/lib/api';
|
||||
import { toast } from 'sonner';
|
||||
import { projectKeys } from './keys';
|
||||
|
||||
export const useCreateProject = createMutationHook(
|
||||
(data: { name: string; description: string; accountId?: string }) =>
|
||||
createProject(data, data.accountId),
|
||||
async (data: { name: string; description: string; accountId?: string }) => {
|
||||
// Check project limits before creating
|
||||
try {
|
||||
const limits = await checkProjectLimits();
|
||||
if (!limits.can_create) {
|
||||
throw new Error(limits.message);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('Project limit')) {
|
||||
toast.error(error.message);
|
||||
throw error;
|
||||
}
|
||||
// For other errors, let the backend handle the limit check
|
||||
}
|
||||
|
||||
return createProject(data, data.accountId);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success('Project created successfully');
|
||||
|
|
|
@ -1606,6 +1606,8 @@ export interface SubscriptionStatus {
|
|||
minutes_limit?: number;
|
||||
cost_limit?: number;
|
||||
current_usage?: number;
|
||||
project_limit?: number; // Added project limit
|
||||
current_project_count?: number; // Added current project count
|
||||
// Fields for scheduled changes
|
||||
has_schedule: boolean;
|
||||
scheduled_plan_name?: string;
|
||||
|
@ -1934,6 +1936,59 @@ export const checkBillingStatus = async (): Promise<BillingStatusResponse> => {
|
|||
}
|
||||
};
|
||||
|
||||
export interface ProjectLimitsResponse {
|
||||
can_create: boolean;
|
||||
message: string;
|
||||
subscription: {
|
||||
price_id: string;
|
||||
plan_name: string;
|
||||
project_limit: number;
|
||||
current_project_count: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const checkProjectLimits = async (): Promise<ProjectLimitsResponse> => {
|
||||
try {
|
||||
const supabase = createClient();
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession();
|
||||
|
||||
if (!session?.access_token) {
|
||||
throw new NoAccessTokenAvailableError();
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/billing/check-project-limits`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.access_token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response
|
||||
.text()
|
||||
.catch(() => 'No error details available');
|
||||
console.error(
|
||||
`Error checking project limits: ${response.status} ${response.statusText}`,
|
||||
errorText,
|
||||
);
|
||||
throw new Error(
|
||||
`Error checking project limits: ${response.statusText} (${response.status})`,
|
||||
);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof NoAccessTokenAvailableError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.error('Failed to check project limits:', error);
|
||||
handleApiError(error, { operation: 'check project limits', resource: 'project limits' });
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Transcription API Types
|
||||
export interface TranscriptionResponse {
|
||||
text: string;
|
||||
|
|
Loading…
Reference in New Issue