Files
smoothschedule/frontend/src/api/platform.ts
poduck 640961904e feat: Add comprehensive permissions to BusinessEditModal
Frontend Changes:
- Add all 5 platform permissions to BusinessEditModal (matching TenantInviteModal)
  - Manage OAuth Credentials
  - Accept Online Payments (Stripe Connect)
  - Use Custom Domain
  - Remove Branding (White Label)
  - API Access
- Add "Coming Soon" feature limits section with 11 future capabilities
  - Video conferencing
  - Event types limits (unlimited or custom)
  - Calendar connections limits (unlimited or custom)
  - External API connections
  - Repeated/recurring events
  - 2FA requirement
  - System logs download
  - Data deletion
  - Masked phone numbers
  - POS system integration
  - Mobile app access
- Update TypeScript interfaces to include all permission fields
  - PlatformBusiness: Add 4 new required boolean fields
  - PlatformBusinessUpdate: Add 4 new optional boolean fields

Backend Changes:
- Update TenantUpdateSerializer to accept all 5 permission fields
  - can_manage_oauth_credentials
  - can_accept_payments
  - can_use_custom_domain
  - can_white_label
  - can_api_access

UI Improvements:
- All permissions displayed with toggle switches and descriptions
- Purple theme for permission toggles
- Gray card backgrounds for visual separation
- "Coming Soon" badge with yellow styling
- Disabled state (opacity-50) for future features
- Proper spacing and layout consistency

Result:
- BusinessEditModal now has complete feature parity with TenantInviteModal
- Platform admins can view and modify all current permissions
- Clear visibility into planned features

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 04:00:42 -05:00

266 lines
6.8 KiB
TypeScript

/**
* Platform API
* API functions for platform-level operations (businesses, users, etc.)
*/
import apiClient from './client';
export interface PlatformBusinessOwner {
id: number;
username: string;
full_name: string;
email: string;
role: string;
}
export interface PlatformBusiness {
id: number;
name: string;
subdomain: string;
tier: string;
is_active: boolean;
created_on: string;
user_count: number;
owner: PlatformBusinessOwner | null;
max_users: number;
max_resources: number;
contact_email?: string;
phone?: string;
// Platform permissions
can_manage_oauth_credentials: boolean;
can_accept_payments: boolean;
can_use_custom_domain: boolean;
can_white_label: boolean;
can_api_access: boolean;
}
export interface PlatformBusinessUpdate {
name?: string;
is_active?: boolean;
subscription_tier?: string;
max_users?: number;
max_resources?: number;
can_manage_oauth_credentials?: boolean;
can_accept_payments?: boolean;
can_use_custom_domain?: boolean;
can_white_label?: boolean;
can_api_access?: boolean;
}
export interface PlatformBusinessCreate {
name: string;
subdomain: string;
subscription_tier?: string;
is_active?: boolean;
max_users?: number;
max_resources?: number;
contact_email?: string;
phone?: string;
can_manage_oauth_credentials?: boolean;
// Owner details (optional)
owner_email?: string;
owner_name?: string;
owner_password?: string;
}
export interface PlatformUser {
id: number;
email: string;
username: string;
name?: string;
role?: string;
is_active: boolean;
is_staff: boolean;
is_superuser: boolean;
business: number | null;
business_name?: string;
business_subdomain?: string;
date_joined: string;
last_login?: string;
}
/**
* Get all businesses (platform admin only)
*/
export const getBusinesses = async (): Promise<PlatformBusiness[]> => {
const response = await apiClient.get<PlatformBusiness[]>('/api/platform/businesses/');
return response.data;
};
/**
* Update a business (platform admin only)
*/
export const updateBusiness = async (
businessId: number,
data: PlatformBusinessUpdate
): Promise<PlatformBusiness> => {
const response = await apiClient.patch<PlatformBusiness>(
`/api/platform/businesses/${businessId}/`,
data
);
return response.data;
};
/**
* Create a new business (platform admin only)
*/
export const createBusiness = async (
data: PlatformBusinessCreate
): Promise<PlatformBusiness> => {
const response = await apiClient.post<PlatformBusiness>(
'/api/platform/businesses/',
data
);
return response.data;
};
/**
* Get all users (platform admin only)
*/
export const getUsers = async (): Promise<PlatformUser[]> => {
const response = await apiClient.get<PlatformUser[]>('/api/platform/users/');
return response.data;
};
/**
* Get users for a specific business
*/
export const getBusinessUsers = async (businessId: number): Promise<PlatformUser[]> => {
const response = await apiClient.get<PlatformUser[]>(`/api/platform/users/?business=${businessId}`);
return response.data;
};
// ============================================================================
// Tenant Invitations
// ============================================================================
export interface TenantInvitation {
id: number;
email: string;
token: string;
status: 'PENDING' | 'ACCEPTED' | 'EXPIRED' | 'CANCELLED';
suggested_business_name: string;
subscription_tier: 'FREE' | 'STARTER' | 'PROFESSIONAL' | 'ENTERPRISE';
custom_max_users: number | null;
custom_max_resources: number | null;
permissions: {
can_manage_oauth_credentials?: boolean;
can_accept_payments?: boolean;
can_use_custom_domain?: boolean;
can_white_label?: boolean;
can_api_access?: boolean;
};
personal_message: string;
invited_by: number;
invited_by_email: string;
created_at: string;
expires_at: string;
accepted_at: string | null;
created_tenant: number | null;
created_tenant_name: string | null;
created_user: number | null;
created_user_email: string | null;
}
export interface TenantInvitationCreate {
email: string;
suggested_business_name?: string;
subscription_tier: 'FREE' | 'STARTER' | 'PROFESSIONAL' | 'ENTERPRISE';
custom_max_users?: number | null;
custom_max_resources?: number | null;
permissions?: {
can_manage_oauth_credentials?: boolean;
can_accept_payments?: boolean;
can_use_custom_domain?: boolean;
can_white_label?: boolean;
can_api_access?: boolean;
};
personal_message?: string;
}
export interface TenantInvitationDetail {
email: string;
suggested_business_name: string;
subscription_tier: string;
effective_max_users: number;
effective_max_resources: number;
permissions: {
can_manage_oauth_credentials?: boolean;
can_accept_payments?: boolean;
can_use_custom_domain?: boolean;
can_white_label?: boolean;
can_api_access?: boolean;
};
expires_at: string;
}
export interface TenantInvitationAccept {
email: string;
password: string;
first_name: string;
last_name: string;
business_name: string;
subdomain: string;
contact_email?: string;
phone?: string;
}
/**
* Get all tenant invitations (platform admin only)
*/
export const getTenantInvitations = async (): Promise<TenantInvitation[]> => {
const response = await apiClient.get<TenantInvitation[]>('/api/platform/tenant-invitations/');
return response.data;
};
/**
* Create a tenant invitation (platform admin only)
*/
export const createTenantInvitation = async (
data: TenantInvitationCreate
): Promise<TenantInvitation> => {
const response = await apiClient.post<TenantInvitation>(
'/api/platform/tenant-invitations/',
data
);
return response.data;
};
/**
* Resend a tenant invitation (platform admin only)
*/
export const resendTenantInvitation = async (invitationId: number): Promise<void> => {
await apiClient.post(`/api/platform/tenant-invitations/${invitationId}/resend/`);
};
/**
* Cancel a tenant invitation (platform admin only)
*/
export const cancelTenantInvitation = async (invitationId: number): Promise<void> => {
await apiClient.post(`/api/platform/tenant-invitations/${invitationId}/cancel/`);
};
/**
* Get invitation details by token (public, no auth required)
*/
export const getInvitationByToken = async (token: string): Promise<TenantInvitationDetail> => {
const response = await apiClient.get<TenantInvitationDetail>(
`/api/platform/tenant-invitations/token/${token}/`
);
return response.data;
};
/**
* Accept an invitation by token (public, no auth required)
*/
export const acceptInvitation = async (
token: string,
data: TenantInvitationAccept
): Promise<{ detail: string }> => {
const response = await apiClient.post<{ detail: string }>(
`/api/platform/tenant-invitations/token/${token}/accept/`,
data
);
return response.data;
};