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,197 @@
/**
* Transaction Analytics Hooks
*
* React Query hooks for fetching and managing transaction analytics data.
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
getTransactions,
getTransaction,
getTransactionSummary,
getStripeCharges,
getStripePayouts,
getStripeBalance,
exportTransactions,
getTransactionDetail,
refundTransaction,
TransactionFilters,
ExportRequest,
RefundRequest,
} from '../api/payments';
/**
* Hook to fetch paginated transaction list with optional filters.
*/
export const useTransactions = (filters?: TransactionFilters) => {
return useQuery({
queryKey: ['transactions', filters],
queryFn: async () => {
const { data } = await getTransactions(filters);
return data;
},
staleTime: 30 * 1000, // 30 seconds
});
};
/**
* Hook to fetch a single transaction by ID.
*/
export const useTransaction = (id: number) => {
return useQuery({
queryKey: ['transaction', id],
queryFn: async () => {
const { data } = await getTransaction(id);
return data;
},
enabled: !!id,
});
};
/**
* Hook to fetch transaction summary/analytics.
*/
export const useTransactionSummary = (filters?: Pick<TransactionFilters, 'start_date' | 'end_date'>) => {
return useQuery({
queryKey: ['transactionSummary', filters],
queryFn: async () => {
const { data } = await getTransactionSummary(filters);
return data;
},
staleTime: 60 * 1000, // 1 minute
});
};
/**
* Hook to fetch Stripe charges directly from Stripe API.
*/
export const useStripeCharges = (limit: number = 20) => {
return useQuery({
queryKey: ['stripeCharges', limit],
queryFn: async () => {
const { data } = await getStripeCharges(limit);
return data;
},
staleTime: 30 * 1000,
});
};
/**
* Hook to fetch Stripe payouts.
*/
export const useStripePayouts = (limit: number = 20) => {
return useQuery({
queryKey: ['stripePayouts', limit],
queryFn: async () => {
const { data } = await getStripePayouts(limit);
return data;
},
staleTime: 30 * 1000,
});
};
/**
* Hook to fetch current Stripe balance.
*/
export const useStripeBalance = () => {
return useQuery({
queryKey: ['stripeBalance'],
queryFn: async () => {
const { data } = await getStripeBalance();
return data;
},
staleTime: 60 * 1000, // 1 minute
refetchInterval: 5 * 60 * 1000, // Refresh every 5 minutes
});
};
/**
* Hook to export transaction data.
* Returns a mutation that triggers file download.
*/
export const useExportTransactions = () => {
return useMutation({
mutationFn: async (request: ExportRequest) => {
const response = await exportTransactions(request);
return response;
},
onSuccess: (response, request) => {
// Create blob URL and trigger download
const blob = new Blob([response.data], { type: response.headers['content-type'] });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// Determine file extension based on format
const extensions: Record<string, string> = {
csv: 'csv',
xlsx: 'xlsx',
pdf: 'pdf',
quickbooks: 'iif',
};
const ext = extensions[request.format] || 'txt';
link.download = `transactions.${ext}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
},
});
};
/**
* Hook to invalidate all transaction-related queries.
* Useful after actions that modify transaction data.
*/
export const useInvalidateTransactions = () => {
const queryClient = useQueryClient();
return () => {
queryClient.invalidateQueries({ queryKey: ['transactions'] });
queryClient.invalidateQueries({ queryKey: ['transactionSummary'] });
queryClient.invalidateQueries({ queryKey: ['stripeCharges'] });
queryClient.invalidateQueries({ queryKey: ['stripePayouts'] });
queryClient.invalidateQueries({ queryKey: ['stripeBalance'] });
queryClient.invalidateQueries({ queryKey: ['transactionDetail'] });
};
};
/**
* Hook to fetch detailed transaction information including refund data.
*/
export const useTransactionDetail = (id: number | null) => {
return useQuery({
queryKey: ['transactionDetail', id],
queryFn: async () => {
if (!id) return null;
const { data } = await getTransactionDetail(id);
return data;
},
enabled: !!id,
staleTime: 10 * 1000, // 10 seconds (refresh often for live data)
});
};
/**
* Hook to issue a refund for a transaction.
* Automatically invalidates transaction queries on success.
*/
export const useRefundTransaction = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ transactionId, request }: { transactionId: number; request?: RefundRequest }) => {
const { data } = await refundTransaction(transactionId, request);
return data;
},
onSuccess: (data, variables) => {
// Invalidate all relevant queries
queryClient.invalidateQueries({ queryKey: ['transactions'] });
queryClient.invalidateQueries({ queryKey: ['transactionSummary'] });
queryClient.invalidateQueries({ queryKey: ['transactionDetail', variables.transactionId] });
queryClient.invalidateQueries({ queryKey: ['stripeCharges'] });
queryClient.invalidateQueries({ queryKey: ['stripeBalance'] });
},
});
};