Initial commit: SmoothSchedule multi-tenant scheduling platform

This commit includes:
- Django backend with multi-tenancy (django-tenants)
- React + TypeScript frontend with Vite
- Platform administration API with role-based access control
- Authentication system with token-based auth
- Quick login dev tools for testing different user roles
- CORS and CSRF configuration for local development
- Docker development environment setup

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
poduck
2025-11-27 01:43:20 -05:00
commit 2e111364a2
567 changed files with 96410 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
/**
* Service Management Hooks
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import apiClient from '../api/client';
import { Service } from '../types';
/**
* Hook to fetch all services for current business
*/
export const useServices = () => {
return useQuery<Service[]>({
queryKey: ['services'],
queryFn: async () => {
const { data } = await apiClient.get('/api/services/');
// Transform backend format to frontend format
return data.map((s: any) => ({
id: String(s.id),
name: s.name,
durationMinutes: s.duration || s.duration_minutes,
price: parseFloat(s.price),
description: s.description || '',
}));
},
});
};
/**
* Hook to get a single service
*/
export const useService = (id: string) => {
return useQuery<Service>({
queryKey: ['services', id],
queryFn: async () => {
const { data } = await apiClient.get(`/api/services/${id}/`);
return {
id: String(data.id),
name: data.name,
durationMinutes: data.duration || data.duration_minutes,
price: parseFloat(data.price),
description: data.description || '',
};
},
enabled: !!id,
});
};
/**
* Hook to create a service
*/
export const useCreateService = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (serviceData: Omit<Service, 'id'>) => {
const backendData = {
name: serviceData.name,
duration: serviceData.durationMinutes,
price: serviceData.price.toString(),
description: serviceData.description,
};
const { data } = await apiClient.post('/api/services/', backendData);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['services'] });
},
});
};
/**
* Hook to update a service
*/
export const useUpdateService = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, updates }: { id: string; updates: Partial<Service> }) => {
const backendData: any = {};
if (updates.name) backendData.name = updates.name;
if (updates.durationMinutes) backendData.duration = updates.durationMinutes;
if (updates.price) backendData.price = updates.price.toString();
if (updates.description !== undefined) backendData.description = updates.description;
const { data } = await apiClient.patch(`/api/services/${id}/`, backendData);
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['services'] });
},
});
};
/**
* Hook to delete a service
*/
export const useDeleteService = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
await apiClient.delete(`/api/services/${id}/`);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['services'] });
},
});
};