- Update Service model to use price_cents/deposit_amount_cents as IntegerField - Add @property methods for backward compatibility (price, deposit_amount return dollars) - Update ServiceSerializer to convert dollars <-> cents on read/write - Add migration to convert column types from numeric to integer - Fix BusinessEditModal to properly use typed PlatformBusiness interface - Add missing feature permission fields to PlatformBusiness TypeScript interface 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
328 lines
8.7 KiB
TypeScript
328 lines
8.7 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;
|
|
email_verified: boolean;
|
|
}
|
|
|
|
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;
|
|
// Feature permissions (optional - returned by API but may not always be present in tests)
|
|
can_add_video_conferencing?: boolean;
|
|
can_connect_to_api?: boolean;
|
|
can_book_repeated_events?: boolean;
|
|
can_require_2fa?: boolean;
|
|
can_download_logs?: boolean;
|
|
can_delete_data?: boolean;
|
|
can_use_sms_reminders?: boolean;
|
|
can_use_masked_phone_numbers?: boolean;
|
|
can_use_pos?: boolean;
|
|
can_use_mobile_app?: boolean;
|
|
can_export_data?: boolean;
|
|
can_use_plugins?: boolean;
|
|
can_use_tasks?: boolean;
|
|
can_create_plugins?: boolean;
|
|
can_use_webhooks?: boolean;
|
|
can_use_calendar_sync?: boolean;
|
|
can_use_contracts?: boolean;
|
|
}
|
|
|
|
export interface PlatformBusinessUpdate {
|
|
name?: string;
|
|
is_active?: boolean;
|
|
subscription_tier?: string;
|
|
max_users?: number;
|
|
max_resources?: number;
|
|
// Platform permissions
|
|
can_manage_oauth_credentials?: boolean;
|
|
can_accept_payments?: boolean;
|
|
can_use_custom_domain?: boolean;
|
|
can_white_label?: boolean;
|
|
can_api_access?: boolean;
|
|
// Feature permissions
|
|
can_add_video_conferencing?: boolean;
|
|
can_connect_to_api?: boolean;
|
|
can_book_repeated_events?: boolean;
|
|
can_require_2fa?: boolean;
|
|
can_download_logs?: boolean;
|
|
can_delete_data?: boolean;
|
|
can_use_sms_reminders?: boolean;
|
|
can_use_masked_phone_numbers?: boolean;
|
|
can_use_pos?: boolean;
|
|
can_use_mobile_app?: boolean;
|
|
can_export_data?: boolean;
|
|
can_use_plugins?: boolean;
|
|
can_use_tasks?: boolean;
|
|
can_create_plugins?: boolean;
|
|
can_use_webhooks?: boolean;
|
|
can_use_calendar_sync?: boolean;
|
|
can_use_contracts?: boolean;
|
|
can_process_refunds?: boolean;
|
|
can_create_packages?: boolean;
|
|
can_use_email_templates?: boolean;
|
|
can_customize_booking_page?: boolean;
|
|
advanced_reporting?: boolean;
|
|
priority_support?: boolean;
|
|
dedicated_support?: boolean;
|
|
sso_enabled?: 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;
|
|
email_verified: 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[]>('/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>(
|
|
`/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>(
|
|
'/platform/businesses/',
|
|
data
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Delete a business/tenant (platform admin only)
|
|
* This permanently deletes the tenant and all associated data
|
|
*/
|
|
export const deleteBusiness = async (businessId: number): Promise<void> => {
|
|
await apiClient.delete(`/platform/businesses/${businessId}/`);
|
|
};
|
|
|
|
/**
|
|
* Get all users (platform admin only)
|
|
*/
|
|
export const getUsers = async (): Promise<PlatformUser[]> => {
|
|
const response = await apiClient.get<PlatformUser[]>('/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[]>(`/platform/users/?business=${businessId}`);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Verify a user's email (platform admin only)
|
|
*/
|
|
export const verifyUserEmail = async (userId: number): Promise<void> => {
|
|
await apiClient.post(`/platform/users/${userId}/verify_email/`);
|
|
};
|
|
|
|
// ============================================================================
|
|
// 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[]>('/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>(
|
|
'/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(`/platform/tenant-invitations/${invitationId}/resend/`);
|
|
};
|
|
|
|
/**
|
|
* Cancel a tenant invitation (platform admin only)
|
|
*/
|
|
export const cancelTenantInvitation = async (invitationId: number): Promise<void> => {
|
|
await apiClient.post(`/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>(
|
|
`/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 }>(
|
|
`/platform/tenant-invitations/token/${token}/accept/`,
|
|
data
|
|
);
|
|
return response.data;
|
|
};
|