feat: Reorganize settings sidebar and add plan-based feature locking
- Add locked state to Plugins sidebar item with plan feature check - Create Branding section in settings with Appearance, Email Templates, Custom Domains - Split Domains page into Booking (URLs, redirects) and Custom Domains (BYOD, purchase) - Add booking_return_url field to Tenant model for customer redirects - Update SidebarItem component to support locked prop with lock icon - Move Email Templates from main sidebar to Settings > Branding - Add communication credits hooks and payment form updates - Add timezone fields migration and various UI improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -193,3 +193,134 @@ export const useCommunicationUsageStats = () => {
|
||||
staleTime: 60 * 1000, // 1 minute
|
||||
});
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Phone Number Management Hooks
|
||||
// =============================================================================
|
||||
|
||||
export interface ProxyPhoneNumber {
|
||||
id: number;
|
||||
phone_number: string;
|
||||
friendly_name: string;
|
||||
status: 'available' | 'assigned' | 'reserved' | 'inactive';
|
||||
monthly_fee_cents: number;
|
||||
capabilities: {
|
||||
voice: boolean;
|
||||
sms: boolean;
|
||||
mms: boolean;
|
||||
};
|
||||
assigned_at: string | null;
|
||||
last_billed_at: string | null;
|
||||
}
|
||||
|
||||
export interface AvailablePhoneNumber {
|
||||
phone_number: string;
|
||||
friendly_name: string;
|
||||
locality: string;
|
||||
region: string;
|
||||
postal_code: string;
|
||||
capabilities: {
|
||||
voice: boolean;
|
||||
sms: boolean;
|
||||
mms: boolean;
|
||||
};
|
||||
monthly_cost_cents: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to list phone numbers assigned to the tenant
|
||||
*/
|
||||
export const usePhoneNumbers = () => {
|
||||
return useQuery<{ numbers: ProxyPhoneNumber[]; count: number }>({
|
||||
queryKey: ['phoneNumbers'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/communication-credits/phone-numbers/');
|
||||
return data;
|
||||
},
|
||||
staleTime: 30 * 1000,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to search for available phone numbers from Twilio
|
||||
*/
|
||||
export const useSearchPhoneNumbers = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (params: {
|
||||
area_code?: string;
|
||||
contains?: string;
|
||||
country?: string;
|
||||
limit?: number;
|
||||
}) => {
|
||||
const { data } = await apiClient.get('/communication-credits/phone-numbers/search/', {
|
||||
params,
|
||||
});
|
||||
return data as { numbers: AvailablePhoneNumber[]; count: number };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to purchase a phone number
|
||||
*/
|
||||
export const usePurchasePhoneNumber = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (params: { phone_number: string; friendly_name?: string }) => {
|
||||
const { data } = await apiClient.post('/communication-credits/phone-numbers/purchase/', params);
|
||||
return data as {
|
||||
success: boolean;
|
||||
phone_number: ProxyPhoneNumber;
|
||||
balance_cents: number;
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['phoneNumbers'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['communicationCredits'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['creditTransactions'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to release (delete) a phone number
|
||||
*/
|
||||
export const useReleasePhoneNumber = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (numberId: number) => {
|
||||
const { data } = await apiClient.delete(`/communication-credits/phone-numbers/${numberId}/`);
|
||||
return data as { success: boolean; message: string };
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['phoneNumbers'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['communicationUsageStats'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to change a phone number to a different one
|
||||
*/
|
||||
export const useChangePhoneNumber = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (params: { numberId: number; new_phone_number: string; friendly_name?: string }) => {
|
||||
const { numberId, ...body } = params;
|
||||
const { data } = await apiClient.post(`/communication-credits/phone-numbers/${numberId}/change/`, body);
|
||||
return data as {
|
||||
success: boolean;
|
||||
phone_number: ProxyPhoneNumber;
|
||||
balance_cents: number;
|
||||
};
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['phoneNumbers'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['communicationCredits'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['creditTransactions'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user