Major updates including: - Customizable dashboard with drag-and-drop widget grid layout - Plan-based feature locking for plugins and tasks - Comprehensive help documentation updates across all pages - Plugin seeding in deployment process for all tenants - Permission synchronization system for subscription plans - QuotaOverageModal component and enhanced UX flows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
258 lines
6.7 KiB
TypeScript
258 lines
6.7 KiB
TypeScript
/**
|
|
* Platform Settings Hooks
|
|
*/
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import apiClient from '../api/client';
|
|
|
|
export interface PlatformSettings {
|
|
stripe_secret_key_masked: string;
|
|
stripe_publishable_key_masked: string;
|
|
stripe_webhook_secret_masked: string;
|
|
stripe_account_id: string;
|
|
stripe_account_name: string;
|
|
stripe_keys_validated_at: string | null;
|
|
stripe_validation_error: string;
|
|
has_stripe_keys: boolean;
|
|
stripe_keys_from_env: boolean;
|
|
email_check_interval_minutes: number;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface GeneralSettingsUpdate {
|
|
email_check_interval_minutes?: number;
|
|
}
|
|
|
|
export interface StripeKeysUpdate {
|
|
stripe_secret_key?: string;
|
|
stripe_publishable_key?: string;
|
|
stripe_webhook_secret?: string;
|
|
}
|
|
|
|
export interface SubscriptionPlan {
|
|
id: number;
|
|
name: string;
|
|
description: string;
|
|
plan_type: 'base' | 'addon';
|
|
stripe_product_id: string;
|
|
stripe_price_id: string;
|
|
price_monthly: string | null;
|
|
price_yearly: string | null;
|
|
business_tier: string;
|
|
features: string[];
|
|
limits: Record<string, any>;
|
|
permissions: Record<string, boolean>;
|
|
transaction_fee_percent: string;
|
|
transaction_fee_fixed: string;
|
|
// Communication pricing
|
|
sms_enabled: boolean;
|
|
sms_price_per_message_cents: number;
|
|
masked_calling_enabled: boolean;
|
|
masked_calling_price_per_minute_cents: number;
|
|
proxy_number_enabled: boolean;
|
|
proxy_number_monthly_fee_cents: number;
|
|
// Default credit settings
|
|
default_auto_reload_enabled: boolean;
|
|
default_auto_reload_threshold_cents: number;
|
|
default_auto_reload_amount_cents: number;
|
|
is_active: boolean;
|
|
is_public: boolean;
|
|
is_most_popular: boolean;
|
|
show_price: boolean;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface SubscriptionPlanCreate {
|
|
name: string;
|
|
description?: string;
|
|
plan_type?: 'base' | 'addon';
|
|
price_monthly?: number | null;
|
|
price_yearly?: number | null;
|
|
business_tier?: string;
|
|
features?: string[];
|
|
limits?: Record<string, any>;
|
|
permissions?: Record<string, boolean>;
|
|
transaction_fee_percent?: number;
|
|
transaction_fee_fixed?: number;
|
|
// Communication pricing
|
|
sms_enabled?: boolean;
|
|
sms_price_per_message_cents?: number;
|
|
masked_calling_enabled?: boolean;
|
|
masked_calling_price_per_minute_cents?: number;
|
|
proxy_number_enabled?: boolean;
|
|
proxy_number_monthly_fee_cents?: number;
|
|
// Default credit settings
|
|
default_auto_reload_enabled?: boolean;
|
|
default_auto_reload_threshold_cents?: number;
|
|
default_auto_reload_amount_cents?: number;
|
|
is_active?: boolean;
|
|
is_public?: boolean;
|
|
is_most_popular?: boolean;
|
|
show_price?: boolean;
|
|
create_stripe_product?: boolean;
|
|
stripe_product_id?: string;
|
|
stripe_price_id?: string;
|
|
}
|
|
|
|
/**
|
|
* Hook to get platform settings
|
|
*/
|
|
export const usePlatformSettings = () => {
|
|
return useQuery<PlatformSettings>({
|
|
queryKey: ['platformSettings'],
|
|
queryFn: async () => {
|
|
const { data } = await apiClient.get('/platform/settings/');
|
|
return data;
|
|
},
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to update general platform settings
|
|
*/
|
|
export const useUpdateGeneralSettings = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (settings: GeneralSettingsUpdate) => {
|
|
const { data } = await apiClient.post('/platform/settings/general/', settings);
|
|
return data;
|
|
},
|
|
onSuccess: (data) => {
|
|
queryClient.setQueryData(['platformSettings'], data);
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to update platform Stripe keys
|
|
*/
|
|
export const useUpdateStripeKeys = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (keys: StripeKeysUpdate) => {
|
|
const { data } = await apiClient.post('/platform/settings/stripe/keys/', keys);
|
|
return data;
|
|
},
|
|
onSuccess: (data) => {
|
|
queryClient.setQueryData(['platformSettings'], data);
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to validate platform Stripe keys
|
|
*/
|
|
export const useValidateStripeKeys = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async () => {
|
|
const { data } = await apiClient.post('/platform/settings/stripe/validate/');
|
|
return data;
|
|
},
|
|
onSuccess: (data) => {
|
|
if (data.settings) {
|
|
queryClient.setQueryData(['platformSettings'], data.settings);
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to get subscription plans
|
|
*/
|
|
export const useSubscriptionPlans = () => {
|
|
return useQuery<SubscriptionPlan[]>({
|
|
queryKey: ['subscriptionPlans'],
|
|
queryFn: async () => {
|
|
const { data } = await apiClient.get('/platform/subscription-plans/');
|
|
return data;
|
|
},
|
|
staleTime: 5 * 60 * 1000,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to create a subscription plan
|
|
*/
|
|
export const useCreateSubscriptionPlan = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (plan: SubscriptionPlanCreate) => {
|
|
const { data } = await apiClient.post('/platform/subscription-plans/', plan);
|
|
return data;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['subscriptionPlans'] });
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to update a subscription plan
|
|
*/
|
|
export const useUpdateSubscriptionPlan = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ id, ...updates }: Partial<SubscriptionPlanCreate> & { id: number }) => {
|
|
const { data } = await apiClient.patch(`/platform/subscription-plans/${id}/`, updates);
|
|
return data;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['subscriptionPlans'] });
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to delete a subscription plan
|
|
*/
|
|
export const useDeleteSubscriptionPlan = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (id: number) => {
|
|
const { data } = await apiClient.delete(`/platform/subscription-plans/${id}/`);
|
|
return data;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['subscriptionPlans'] });
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to sync plans with Stripe
|
|
*/
|
|
export const useSyncPlansWithStripe = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async () => {
|
|
const { data } = await apiClient.post('/platform/subscription-plans/sync_with_stripe/');
|
|
return data;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['subscriptionPlans'] });
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to sync a plan's permissions to all tenants on that plan
|
|
*/
|
|
export const useSyncPlanToTenants = () => {
|
|
return useMutation({
|
|
mutationFn: async (planId: number) => {
|
|
const { data } = await apiClient.post(`/platform/subscription-plans/${planId}/sync_tenants/`);
|
|
return data as { message: string; tenant_count: number };
|
|
},
|
|
});
|
|
};
|