Major features: - Add drag-and-drop photo gallery to Service create/edit modals - Add Resource Types management section to Settings (CRUD for custom types) - Add edit icon consistency to Resources table (pencil icon in actions) - Improve Services page with drag-to-reorder and customer preview mockup Backend changes: - Add photos JSONField to Service model with migration - Add ResourceType model with category (STAFF/OTHER), description fields - Add ResourceTypeViewSet with CRUD operations - Add service reorder endpoint for display order Frontend changes: - Services page: two-column layout, drag-reorder, photo upload - Settings page: Resource Types tab with full CRUD modal - Resources page: Edit icon in actions column instead of row click - Sidebar: Payments link visibility based on role and paymentsEnabled - Update types.ts with Service.photos and ResourceTypeDefinition Note: Removed photos from ResourceType (kept only for Service) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
149 lines
5.2 KiB
TypeScript
149 lines
5.2 KiB
TypeScript
/**
|
|
* Business Management Hooks
|
|
*/
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import apiClient from '../api/client';
|
|
import { Business } from '../types';
|
|
import { getCookie } from '../utils/cookies';
|
|
|
|
/**
|
|
* Hook to get current business
|
|
*/
|
|
export const useCurrentBusiness = () => {
|
|
// Check token outside the query to use as dependency
|
|
const token = getCookie('access_token');
|
|
|
|
return useQuery<Business | null>({
|
|
queryKey: ['currentBusiness', !!token], // Include token presence in query key to refetch when token changes
|
|
queryFn: async () => {
|
|
// Check if token exists before making request (from cookie)
|
|
const currentToken = getCookie('access_token');
|
|
if (!currentToken) {
|
|
return null; // No token, return null instead of making request
|
|
}
|
|
|
|
const { data } = await apiClient.get('/api/business/current/');
|
|
|
|
// Transform backend format to frontend format
|
|
return {
|
|
id: String(data.id),
|
|
name: data.name,
|
|
subdomain: data.subdomain,
|
|
primaryColor: data.primary_color || '#3B82F6', // Blue-500 default
|
|
secondaryColor: data.secondary_color || '#1E40AF', // Blue-800 default
|
|
logoUrl: data.logo_url,
|
|
emailLogoUrl: data.email_logo_url,
|
|
logoDisplayMode: data.logo_display_mode || 'text-only',
|
|
whitelabelEnabled: data.whitelabel_enabled,
|
|
plan: data.tier, // Map tier to plan
|
|
status: data.status,
|
|
joinedAt: data.created_at ? new Date(data.created_at) : undefined,
|
|
resourcesCanReschedule: data.resources_can_reschedule,
|
|
requirePaymentMethodToBook: data.require_payment_method_to_book,
|
|
cancellationWindowHours: data.cancellation_window_hours,
|
|
lateCancellationFeePercent: data.late_cancellation_fee_percent,
|
|
initialSetupComplete: data.initial_setup_complete,
|
|
websitePages: data.website_pages || {},
|
|
customerDashboardContent: data.customer_dashboard_content || [],
|
|
};
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to update business settings
|
|
*/
|
|
export const useUpdateBusiness = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (updates: Partial<Business>) => {
|
|
const backendData: any = {};
|
|
|
|
// Map frontend fields to backend fields
|
|
if (updates.name) backendData.name = updates.name;
|
|
if (updates.primaryColor) backendData.primary_color = updates.primaryColor;
|
|
if (updates.secondaryColor) backendData.secondary_color = updates.secondaryColor;
|
|
if (updates.logoUrl !== undefined) backendData.logo_url = updates.logoUrl;
|
|
if (updates.emailLogoUrl !== undefined) backendData.email_logo_url = updates.emailLogoUrl;
|
|
if (updates.logoDisplayMode !== undefined) backendData.logo_display_mode = updates.logoDisplayMode;
|
|
if (updates.whitelabelEnabled !== undefined) {
|
|
backendData.whitelabel_enabled = updates.whitelabelEnabled;
|
|
}
|
|
if (updates.resourcesCanReschedule !== undefined) {
|
|
backendData.resources_can_reschedule = updates.resourcesCanReschedule;
|
|
}
|
|
if (updates.requirePaymentMethodToBook !== undefined) {
|
|
backendData.require_payment_method_to_book = updates.requirePaymentMethodToBook;
|
|
}
|
|
if (updates.cancellationWindowHours !== undefined) {
|
|
backendData.cancellation_window_hours = updates.cancellationWindowHours;
|
|
}
|
|
if (updates.lateCancellationFeePercent !== undefined) {
|
|
backendData.late_cancellation_fee_percent = updates.lateCancellationFeePercent;
|
|
}
|
|
if (updates.initialSetupComplete !== undefined) {
|
|
backendData.initial_setup_complete = updates.initialSetupComplete;
|
|
}
|
|
if (updates.websitePages !== undefined) {
|
|
backendData.website_pages = updates.websitePages;
|
|
}
|
|
if (updates.customerDashboardContent !== undefined) {
|
|
backendData.customer_dashboard_content = updates.customerDashboardContent;
|
|
}
|
|
|
|
const { data } = await apiClient.patch('/api/business/current/update/', backendData);
|
|
return data;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['currentBusiness'] });
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to get all resources for the current business
|
|
*/
|
|
export const useResources = () => {
|
|
return useQuery({
|
|
queryKey: ['resources'],
|
|
queryFn: async () => {
|
|
const { data } = await apiClient.get('/api/resources/');
|
|
return data;
|
|
},
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to create a new resource
|
|
*/
|
|
export const useCreateResource = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (resourceData: { name: string; type: string; user_id?: string }) => {
|
|
const { data } = await apiClient.post('/api/resources/', resourceData);
|
|
return data;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['resources'] });
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hook to get all users for the current business
|
|
*/
|
|
export const useBusinessUsers = () => {
|
|
return useQuery({
|
|
queryKey: ['businessUsers'],
|
|
queryFn: async () => {
|
|
const { data } = await apiClient.get('/api/staff/');
|
|
return data;
|
|
},
|
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
});
|
|
};
|