feat: Quota overage system, updated tier pricing, and communication addons
Quota Overage System: - Add QuotaOverage model for tracking resource/user quota overages - Implement 30-day grace period with email notifications (immediate, 7-day, 1-day) - Add QuotaWarningBanner component in BusinessLayout - Add QuotaSettings page for managing overages and archiving resources - Add Celery tasks for automated quota checks and expiration handling - Add quota management API endpoints Updated Tier Pricing (Stripe: 2.9% + $0.30): - Free: No payments (requires addon) - Starter: 4% + $0.40 - Professional: 3.5% + $0.35 - Business: 3.25% + $0.32 - Enterprise: 3% + $0.30 New Subscription Addons: - Online Payments ($5/mo + 5% + $0.50) - for Free tier - SMS Notifications ($10/mo) - enables SMS reminders - Masked Calling ($15/mo) - enables anonymous calling BusinessEditModal Improvements: - Increased width to match PlanModal (max-w-3xl) - Added all tier options with auto-update on tier change - Added limits configuration and permissions sections Backend Fixes: - Fixed SubscriptionPlan serializer to include all communication fields - Allow blank business_tier for addon plans - Added migration for business_tier field changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
103
frontend/src/api/quota.ts
Normal file
103
frontend/src/api/quota.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Quota Management API
|
||||
*/
|
||||
|
||||
import apiClient from './client';
|
||||
import { QuotaOverage } from './auth';
|
||||
|
||||
export interface QuotaUsage {
|
||||
current: number;
|
||||
limit: number;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export interface QuotaStatus {
|
||||
active_overages: QuotaOverage[];
|
||||
usage: Record<string, QuotaUsage>;
|
||||
}
|
||||
|
||||
export interface QuotaResource {
|
||||
id: number;
|
||||
name: string;
|
||||
email?: string;
|
||||
role?: string;
|
||||
type?: string;
|
||||
duration?: number;
|
||||
price?: string;
|
||||
created_at: string | null;
|
||||
is_archived: boolean;
|
||||
archived_at: string | null;
|
||||
}
|
||||
|
||||
export interface QuotaResourcesResponse {
|
||||
quota_type: string;
|
||||
resources: QuotaResource[];
|
||||
}
|
||||
|
||||
export interface ArchiveResponse {
|
||||
archived_count: number;
|
||||
current_usage: number;
|
||||
limit: number;
|
||||
is_resolved: boolean;
|
||||
}
|
||||
|
||||
export interface QuotaOverageDetail extends QuotaOverage {
|
||||
status: string;
|
||||
created_at: string;
|
||||
initial_email_sent_at: string | null;
|
||||
week_reminder_sent_at: string | null;
|
||||
day_reminder_sent_at: string | null;
|
||||
archived_resource_ids: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current quota status
|
||||
*/
|
||||
export const getQuotaStatus = async (): Promise<QuotaStatus> => {
|
||||
const response = await apiClient.get<QuotaStatus>('/quota/status/');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get resources for a specific quota type
|
||||
*/
|
||||
export const getQuotaResources = async (quotaType: string): Promise<QuotaResourcesResponse> => {
|
||||
const response = await apiClient.get<QuotaResourcesResponse>(`/quota/resources/${quotaType}/`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Archive resources to resolve quota overage
|
||||
*/
|
||||
export const archiveResources = async (
|
||||
quotaType: string,
|
||||
resourceIds: number[]
|
||||
): Promise<ArchiveResponse> => {
|
||||
const response = await apiClient.post<ArchiveResponse>('/quota/archive/', {
|
||||
quota_type: quotaType,
|
||||
resource_ids: resourceIds,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unarchive a resource
|
||||
*/
|
||||
export const unarchiveResource = async (
|
||||
quotaType: string,
|
||||
resourceId: number
|
||||
): Promise<{ success: boolean; resource_id: number }> => {
|
||||
const response = await apiClient.post('/quota/unarchive/', {
|
||||
quota_type: quotaType,
|
||||
resource_id: resourceId,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get details for a specific overage
|
||||
*/
|
||||
export const getOverageDetail = async (overageId: number): Promise<QuotaOverageDetail> => {
|
||||
const response = await apiClient.get<QuotaOverageDetail>(`/quota/overages/${overageId}/`);
|
||||
return response.data;
|
||||
};
|
||||
Reference in New Issue
Block a user