Fix double /api/ prefix in API endpoint calls
When VITE_API_URL=/api, axios baseURL is already set to /api. However, all endpoint calls included the /api/ prefix, creating double paths like /api/api/auth/login/. Removed /api/ prefix from 81 API endpoint calls across 22 files: - src/api/auth.ts - Fixed login, logout, me, refresh, hijack endpoints - src/api/client.ts - Fixed token refresh endpoint - src/api/profile.ts - Fixed all profile, email, password, MFA, sessions endpoints - src/hooks/*.ts - Fixed all remaining API calls (users, appointments, resources, etc) - src/pages/*.tsx - Fixed signup and email verification endpoints This ensures API requests use the correct path: /api/auth/login/ instead of /api/api/auth/login/ 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -77,26 +77,26 @@ export const SCOPE_PRESETS = {
|
||||
|
||||
// API Functions
|
||||
const fetchApiTokens = async (): Promise<APIToken[]> => {
|
||||
const response = await apiClient.get('/api/v1/tokens/');
|
||||
const response = await apiClient.get('/v1/tokens/');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const createApiToken = async (data: CreateTokenData): Promise<APITokenCreateResponse> => {
|
||||
const response = await apiClient.post('/api/v1/tokens/', data);
|
||||
const response = await apiClient.post('/v1/tokens/', data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const revokeApiToken = async (tokenId: string): Promise<void> => {
|
||||
await apiClient.delete(`/api/v1/tokens/${tokenId}/`);
|
||||
await apiClient.delete(`/v1/tokens/${tokenId}/`);
|
||||
};
|
||||
|
||||
const updateApiToken = async ({ tokenId, data }: { tokenId: string; data: Partial<CreateTokenData> & { is_active?: boolean } }): Promise<APIToken> => {
|
||||
const response = await apiClient.patch(`/api/v1/tokens/${tokenId}/`, data);
|
||||
const response = await apiClient.patch(`/v1/tokens/${tokenId}/`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const fetchTestTokensForDocs = async (): Promise<TestTokenForDocs[]> => {
|
||||
const response = await apiClient.get('/api/v1/tokens/test-tokens/');
|
||||
const response = await apiClient.get('/v1/tokens/test-tokens/');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { getCookie } from '../utils/cookies';
|
||||
import { getSubdomain } from '../api/config';
|
||||
import { getWebSocketUrl } from '../utils/domain';
|
||||
import { Appointment } from '../types';
|
||||
|
||||
interface WebSocketMessage {
|
||||
@@ -87,7 +88,7 @@ export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions
|
||||
}, [onConnected, onDisconnected, onError]);
|
||||
|
||||
// Get WebSocket URL - not a callback to avoid recreating
|
||||
const getWebSocketUrl = () => {
|
||||
const getWsUrl = () => {
|
||||
const token = getCookie('access_token');
|
||||
const subdomain = getSubdomain();
|
||||
|
||||
@@ -95,11 +96,8 @@ export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine WebSocket host - use api subdomain for WebSocket
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsHost = `api.lvh.me:8000`; // In production, this would come from config
|
||||
|
||||
return `${wsProtocol}//${wsHost}/ws/appointments/?token=${token}&subdomain=${subdomain}`;
|
||||
// Use the getWebSocketUrl utility from domain.ts
|
||||
return `${getWebSocketUrl()}appointments/?token=${token}&subdomain=${subdomain}`;
|
||||
};
|
||||
|
||||
const updateQueryCache = useCallback((message: WebSocketMessage) => {
|
||||
@@ -160,7 +158,7 @@ export function useAppointmentWebSocket(options: UseAppointmentWebSocketOptions
|
||||
return;
|
||||
}
|
||||
|
||||
const url = getWebSocketUrl();
|
||||
const url = getWsUrl();
|
||||
if (!url) {
|
||||
console.log('WebSocket: Missing token or subdomain, skipping connection');
|
||||
return;
|
||||
|
||||
@@ -39,7 +39,7 @@ export const useAppointments = (filters?: AppointmentFilters) => {
|
||||
params.append('end_date', endOfDay.toISOString());
|
||||
}
|
||||
|
||||
const { data } = await apiClient.get(`/api/appointments/?${params}`);
|
||||
const { data } = await apiClient.get(`/appointments/?${params}`);
|
||||
|
||||
// Transform backend format to frontend format
|
||||
return data.map((a: any) => ({
|
||||
@@ -73,7 +73,7 @@ export const useAppointment = (id: string) => {
|
||||
return useQuery<Appointment>({
|
||||
queryKey: ['appointments', id],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get(`/api/appointments/${id}/`);
|
||||
const { data } = await apiClient.get(`/appointments/${id}/`);
|
||||
|
||||
return {
|
||||
id: String(data.id),
|
||||
@@ -115,7 +115,7 @@ export const useCreateAppointment = () => {
|
||||
backendData.customer = parseInt(appointmentData.customerId);
|
||||
}
|
||||
|
||||
const { data } = await apiClient.post('/api/appointments/', backendData);
|
||||
const { data } = await apiClient.post('/appointments/', backendData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -154,7 +154,7 @@ export const useUpdateAppointment = () => {
|
||||
if (updates.status) backendData.status = updates.status;
|
||||
if (updates.notes !== undefined) backendData.notes = updates.notes;
|
||||
|
||||
const { data } = await apiClient.patch(`/api/appointments/${id}/`, backendData);
|
||||
const { data } = await apiClient.patch(`/appointments/${id}/`, backendData);
|
||||
return data;
|
||||
},
|
||||
// Optimistic update: update UI immediately before API call completes
|
||||
@@ -208,7 +208,7 @@ export const useDeleteAppointment = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
await apiClient.delete(`/api/appointments/${id}/`);
|
||||
await apiClient.delete(`/appointments/${id}/`);
|
||||
return id;
|
||||
},
|
||||
// Optimistic update: remove from UI immediately
|
||||
@@ -264,7 +264,7 @@ export const useRescheduleAppointment = () => {
|
||||
newStartTime: Date;
|
||||
newResourceId?: string | null;
|
||||
}) => {
|
||||
const appointment = await apiClient.get(`/api/appointments/${id}/`);
|
||||
const appointment = await apiClient.get(`/appointments/${id}/`);
|
||||
const durationMinutes = appointment.data.duration_minutes;
|
||||
|
||||
return updateMutation.mutateAsync({
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
MasqueradeStackEntry
|
||||
} from '../api/auth';
|
||||
import { getCookie, setCookie, deleteCookie } from '../utils/cookies';
|
||||
import { getBaseDomain, buildSubdomainUrl } from '../utils/domain';
|
||||
|
||||
/**
|
||||
* Helper hook to set auth tokens (used by invitation acceptance)
|
||||
@@ -67,7 +68,7 @@ export const useLogin = () => {
|
||||
return useMutation({
|
||||
mutationFn: login,
|
||||
onSuccess: (data) => {
|
||||
// Store tokens in cookies (domain=.lvh.me for cross-subdomain access)
|
||||
// Store tokens in cookies for cross-subdomain access
|
||||
setCookie('access_token', data.access, 7);
|
||||
setCookie('refresh_token', data.refresh, 7);
|
||||
|
||||
@@ -132,6 +133,7 @@ export const useMasquerade = () => {
|
||||
const user = data.user;
|
||||
const currentHostname = window.location.hostname;
|
||||
const currentPort = window.location.port;
|
||||
const baseDomain = getBaseDomain();
|
||||
|
||||
let targetSubdomain: string | null = null;
|
||||
|
||||
@@ -141,13 +143,14 @@ export const useMasquerade = () => {
|
||||
targetSubdomain = user.business_subdomain;
|
||||
}
|
||||
|
||||
const needsRedirect = targetSubdomain && currentHostname !== `${targetSubdomain}.lvh.me`;
|
||||
const needsRedirect = targetSubdomain && currentHostname !== `${targetSubdomain}.${baseDomain}`;
|
||||
|
||||
if (needsRedirect) {
|
||||
// CRITICAL: Clear the session cookie BEFORE redirect
|
||||
// Call logout API to clear HttpOnly sessionid cookie
|
||||
try {
|
||||
await fetch('http://api.lvh.me:8000/api/auth/logout/', {
|
||||
const apiUrl = import.meta.env.VITE_API_URL || `${window.location.protocol}//${baseDomain}`;
|
||||
await fetch(`${apiUrl}/api/auth/logout/`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
@@ -155,10 +158,9 @@ export const useMasquerade = () => {
|
||||
// Continue anyway
|
||||
}
|
||||
|
||||
const portStr = currentPort ? `:${currentPort}` : '';
|
||||
// Pass tokens AND masquerading stack in URL (for cross-domain transfer)
|
||||
const stackEncoded = encodeURIComponent(JSON.stringify(data.masquerade_stack || []));
|
||||
const redirectUrl = `http://${targetSubdomain}.lvh.me${portStr}/?access_token=${data.access}&refresh_token=${data.refresh}&masquerade_stack=${stackEncoded}`;
|
||||
const redirectUrl = buildSubdomainUrl(targetSubdomain, `/?access_token=${data.access}&refresh_token=${data.refresh}&masquerade_stack=${stackEncoded}`);
|
||||
|
||||
window.location.href = redirectUrl;
|
||||
return;
|
||||
@@ -204,6 +206,7 @@ export const useStopMasquerade = () => {
|
||||
const user = data.user;
|
||||
const currentHostname = window.location.hostname;
|
||||
const currentPort = window.location.port;
|
||||
const baseDomain = getBaseDomain();
|
||||
|
||||
let targetSubdomain: string | null = null;
|
||||
|
||||
@@ -213,12 +216,13 @@ export const useStopMasquerade = () => {
|
||||
targetSubdomain = user.business_subdomain;
|
||||
}
|
||||
|
||||
const needsRedirect = targetSubdomain && currentHostname !== `${targetSubdomain}.lvh.me`;
|
||||
const needsRedirect = targetSubdomain && currentHostname !== `${targetSubdomain}.${baseDomain}`;
|
||||
|
||||
if (needsRedirect) {
|
||||
// CRITICAL: Clear the session cookie BEFORE redirect
|
||||
try {
|
||||
await fetch('http://api.lvh.me:8000/api/auth/logout/', {
|
||||
const apiUrl = import.meta.env.VITE_API_URL || `${window.location.protocol}//${baseDomain}`;
|
||||
await fetch(`${apiUrl}/api/auth/logout/`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
@@ -226,10 +230,9 @@ export const useStopMasquerade = () => {
|
||||
// Continue anyway
|
||||
}
|
||||
|
||||
const portStr = currentPort ? `:${currentPort}` : '';
|
||||
// Pass tokens AND masquerading stack in URL (for cross-domain transfer)
|
||||
const stackEncoded = encodeURIComponent(JSON.stringify(data.masquerade_stack || []));
|
||||
const redirectUrl = `http://${targetSubdomain}.lvh.me${portStr}/?access_token=${data.access}&refresh_token=${data.refresh}&masquerade_stack=${stackEncoded}`;
|
||||
const redirectUrl = buildSubdomainUrl(targetSubdomain, `/?access_token=${data.access}&refresh_token=${data.refresh}&masquerade_stack=${stackEncoded}`);
|
||||
|
||||
window.location.href = redirectUrl;
|
||||
return;
|
||||
|
||||
@@ -23,7 +23,7 @@ export const useCurrentBusiness = () => {
|
||||
return null; // No token, return null instead of making request
|
||||
}
|
||||
|
||||
const { data } = await apiClient.get('/api/business/current/');
|
||||
const { data } = await apiClient.get('/business/current/');
|
||||
|
||||
// Transform backend format to frontend format
|
||||
return {
|
||||
@@ -96,7 +96,7 @@ export const useUpdateBusiness = () => {
|
||||
backendData.customer_dashboard_content = updates.customerDashboardContent;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.patch('/api/business/current/update/', backendData);
|
||||
const { data } = await apiClient.patch('/business/current/update/', backendData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -112,7 +112,7 @@ export const useResources = () => {
|
||||
return useQuery({
|
||||
queryKey: ['resources'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/api/resources/');
|
||||
const { data } = await apiClient.get('/resources/');
|
||||
return data;
|
||||
},
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
@@ -127,7 +127,7 @@ export const useCreateResource = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (resourceData: { name: string; type: string; user_id?: string }) => {
|
||||
const { data } = await apiClient.post('/api/resources/', resourceData);
|
||||
const { data } = await apiClient.post('/resources/', resourceData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -143,7 +143,7 @@ export const useBusinessUsers = () => {
|
||||
return useQuery({
|
||||
queryKey: ['businessUsers'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/api/staff/');
|
||||
const { data } = await apiClient.get('/staff/');
|
||||
return data;
|
||||
},
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
|
||||
@@ -22,7 +22,7 @@ export const useCustomers = (filters?: CustomerFilters) => {
|
||||
if (filters?.status) params.append('status', filters.status);
|
||||
if (filters?.search) params.append('search', filters.search);
|
||||
|
||||
const { data } = await apiClient.get(`/api/customers/?${params}`);
|
||||
const { data } = await apiClient.get(`/customers/?${params}`);
|
||||
|
||||
// Transform backend format to frontend format
|
||||
return data.map((c: any) => ({
|
||||
@@ -66,7 +66,7 @@ export const useCreateCustomer = () => {
|
||||
tags: customerData.tags,
|
||||
};
|
||||
|
||||
const { data } = await apiClient.post('/api/customers/', backendData);
|
||||
const { data } = await apiClient.post('/customers/', backendData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -93,7 +93,7 @@ export const useUpdateCustomer = () => {
|
||||
tags: updates.tags,
|
||||
};
|
||||
|
||||
const { data } = await apiClient.patch(`/api/customers/${id}/`, backendData);
|
||||
const { data } = await apiClient.patch(`/customers/${id}/`, backendData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -110,7 +110,7 @@ export const useDeleteCustomer = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
await apiClient.delete(`/api/customers/${id}/`);
|
||||
await apiClient.delete(`/customers/${id}/`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['customers'] });
|
||||
|
||||
@@ -60,7 +60,7 @@ export const useInvitations = () => {
|
||||
return useQuery<StaffInvitation[]>({
|
||||
queryKey: ['invitations'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/api/staff/invitations/');
|
||||
const { data } = await apiClient.get('/staff/invitations/');
|
||||
return data;
|
||||
},
|
||||
});
|
||||
@@ -74,7 +74,7 @@ export const useCreateInvitation = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (invitationData: CreateInvitationData) => {
|
||||
const { data } = await apiClient.post('/api/staff/invitations/', invitationData);
|
||||
const { data } = await apiClient.post('/staff/invitations/', invitationData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -91,7 +91,7 @@ export const useCancelInvitation = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (invitationId: number) => {
|
||||
await apiClient.delete(`/api/staff/invitations/${invitationId}/`);
|
||||
await apiClient.delete(`/staff/invitations/${invitationId}/`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['invitations'] });
|
||||
@@ -105,7 +105,7 @@ export const useCancelInvitation = () => {
|
||||
export const useResendInvitation = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (invitationId: number) => {
|
||||
const { data } = await apiClient.post(`/api/staff/invitations/${invitationId}/resend/`);
|
||||
const { data } = await apiClient.post(`/staff/invitations/${invitationId}/resend/`);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
@@ -118,7 +118,7 @@ export const useInvitationDetails = (token: string | null) => {
|
||||
return useQuery<InvitationDetails>({
|
||||
queryKey: ['invitation', token],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get(`/api/staff/invitations/token/${token}/`);
|
||||
const { data } = await apiClient.get(`/staff/invitations/token/${token}/`);
|
||||
return data;
|
||||
},
|
||||
enabled: !!token,
|
||||
@@ -142,7 +142,7 @@ export const useAcceptInvitation = () => {
|
||||
lastName: string;
|
||||
password: string;
|
||||
}) => {
|
||||
const { data } = await apiClient.post(`/api/staff/invitations/token/${token}/accept/`, {
|
||||
const { data } = await apiClient.post(`/staff/invitations/token/${token}/accept/`, {
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
password,
|
||||
@@ -158,7 +158,7 @@ export const useAcceptInvitation = () => {
|
||||
export const useDeclineInvitation = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (token: string) => {
|
||||
const { data } = await apiClient.post(`/api/staff/invitations/token/${token}/decline/`);
|
||||
const { data } = await apiClient.post(`/staff/invitations/token/${token}/decline/`);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -67,7 +67,7 @@ export const usePlatformSettings = () => {
|
||||
return useQuery<PlatformSettings>({
|
||||
queryKey: ['platformSettings'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/api/platform/settings/');
|
||||
const { data } = await apiClient.get('/platform/settings/');
|
||||
return data;
|
||||
},
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
@@ -82,7 +82,7 @@ export const useUpdateStripeKeys = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (keys: StripeKeysUpdate) => {
|
||||
const { data } = await apiClient.post('/api/platform/settings/stripe/keys/', keys);
|
||||
const { data } = await apiClient.post('/platform/settings/stripe/keys/', keys);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
@@ -99,7 +99,7 @@ export const useValidateStripeKeys = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiClient.post('/api/platform/settings/stripe/validate/');
|
||||
const { data } = await apiClient.post('/platform/settings/stripe/validate/');
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
@@ -117,7 +117,7 @@ export const useSubscriptionPlans = () => {
|
||||
return useQuery<SubscriptionPlan[]>({
|
||||
queryKey: ['subscriptionPlans'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/api/platform/subscription-plans/');
|
||||
const { data } = await apiClient.get('/platform/subscription-plans/');
|
||||
return data;
|
||||
},
|
||||
staleTime: 5 * 60 * 1000,
|
||||
@@ -132,7 +132,7 @@ export const useCreateSubscriptionPlan = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (plan: SubscriptionPlanCreate) => {
|
||||
const { data } = await apiClient.post('/api/platform/subscription-plans/', plan);
|
||||
const { data } = await apiClient.post('/platform/subscription-plans/', plan);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -149,7 +149,7 @@ export const useUpdateSubscriptionPlan = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, ...updates }: Partial<SubscriptionPlan> & { id: number }) => {
|
||||
const { data } = await apiClient.patch(`/api/platform/subscription-plans/${id}/`, updates);
|
||||
const { data } = await apiClient.patch(`/platform/subscription-plans/${id}/`, updates);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -166,7 +166,7 @@ export const useDeleteSubscriptionPlan = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: number) => {
|
||||
const { data } = await apiClient.delete(`/api/platform/subscription-plans/${id}/`);
|
||||
const { data } = await apiClient.delete(`/platform/subscription-plans/${id}/`);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -183,7 +183,7 @@ export const useSyncPlansWithStripe = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
const { data } = await apiClient.post('/api/platform/subscription-plans/sync_with_stripe/');
|
||||
const { data } = await apiClient.post('/platform/subscription-plans/sync_with_stripe/');
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ export const useResourceTypes = () => {
|
||||
return useQuery<ResourceTypeDefinition[]>({
|
||||
queryKey: ['resourceTypes'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/api/resource-types/');
|
||||
const { data } = await apiClient.get('/resource-types/');
|
||||
return data;
|
||||
},
|
||||
// Provide default types if API doesn't have them yet
|
||||
@@ -48,7 +48,7 @@ export const useCreateResourceType = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (newType: Omit<ResourceTypeDefinition, 'id' | 'isDefault'>) => {
|
||||
const { data } = await apiClient.post('/api/resource-types/', newType);
|
||||
const { data } = await apiClient.post('/resource-types/', newType);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -65,7 +65,7 @@ export const useUpdateResourceType = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, updates }: { id: string; updates: Partial<ResourceTypeDefinition> }) => {
|
||||
const { data } = await apiClient.patch(`/api/resource-types/${id}/`, updates);
|
||||
const { data } = await apiClient.patch(`/resource-types/${id}/`, updates);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -82,7 +82,7 @@ export const useDeleteResourceType = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
await apiClient.delete(`/api/resource-types/${id}/`);
|
||||
await apiClient.delete(`/resource-types/${id}/`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['resourceTypes'] });
|
||||
|
||||
@@ -20,7 +20,7 @@ export const useResources = (filters?: ResourceFilters) => {
|
||||
const params = new URLSearchParams();
|
||||
if (filters?.type) params.append('type', filters.type);
|
||||
|
||||
const { data } = await apiClient.get(`/api/resources/?${params}`);
|
||||
const { data } = await apiClient.get(`/resources/?${params}`);
|
||||
|
||||
// Transform backend format to frontend format
|
||||
return data.map((r: any) => ({
|
||||
@@ -42,7 +42,7 @@ export const useResource = (id: string) => {
|
||||
return useQuery<Resource>({
|
||||
queryKey: ['resources', id],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get(`/api/resources/${id}/`);
|
||||
const { data } = await apiClient.get(`/resources/${id}/`);
|
||||
|
||||
return {
|
||||
id: String(data.id),
|
||||
@@ -72,7 +72,7 @@ export const useCreateResource = () => {
|
||||
timezone: 'UTC', // Default timezone
|
||||
};
|
||||
|
||||
const { data } = await apiClient.post('/api/resources/', backendData);
|
||||
const { data } = await apiClient.post('/resources/', backendData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -102,7 +102,7 @@ export const useUpdateResource = () => {
|
||||
backendData.saved_lane_count = updates.savedLaneCount;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.patch(`/api/resources/${id}/`, backendData);
|
||||
const { data } = await apiClient.patch(`/resources/${id}/`, backendData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -119,7 +119,7 @@ export const useDeleteResource = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
await apiClient.delete(`/api/resources/${id}/`);
|
||||
await apiClient.delete(`/resources/${id}/`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['resources'] });
|
||||
|
||||
@@ -13,7 +13,7 @@ export const useServices = () => {
|
||||
return useQuery<Service[]>({
|
||||
queryKey: ['services'],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get('/api/services/');
|
||||
const { data } = await apiClient.get('/services/');
|
||||
|
||||
// Transform backend format to frontend format
|
||||
return data.map((s: any) => ({
|
||||
@@ -37,7 +37,7 @@ export const useService = (id: string) => {
|
||||
return useQuery<Service>({
|
||||
queryKey: ['services', id],
|
||||
queryFn: async () => {
|
||||
const { data } = await apiClient.get(`/api/services/${id}/`);
|
||||
const { data } = await apiClient.get(`/services/${id}/`);
|
||||
|
||||
return {
|
||||
id: String(data.id),
|
||||
@@ -70,7 +70,7 @@ export const useCreateService = () => {
|
||||
photos: serviceData.photos || [],
|
||||
};
|
||||
|
||||
const { data } = await apiClient.post('/api/services/', backendData);
|
||||
const { data } = await apiClient.post('/services/', backendData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -94,7 +94,7 @@ export const useUpdateService = () => {
|
||||
if (updates.description !== undefined) backendData.description = updates.description;
|
||||
if (updates.photos !== undefined) backendData.photos = updates.photos;
|
||||
|
||||
const { data } = await apiClient.patch(`/api/services/${id}/`, backendData);
|
||||
const { data } = await apiClient.patch(`/services/${id}/`, backendData);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -111,7 +111,7 @@ export const useDeleteService = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
await apiClient.delete(`/api/services/${id}/`);
|
||||
await apiClient.delete(`/services/${id}/`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['services'] });
|
||||
@@ -129,7 +129,7 @@ export const useReorderServices = () => {
|
||||
mutationFn: async (orderedIds: string[]) => {
|
||||
// Convert string IDs to numbers for the backend
|
||||
const order = orderedIds.map(id => parseInt(id, 10));
|
||||
const { data } = await apiClient.post('/api/services/reorder/', { order });
|
||||
const { data } = await apiClient.post('/services/reorder/', { order });
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { getCookie } from '../utils/cookies';
|
||||
import { getWebSocketUrl } from '../utils/domain';
|
||||
import { UserEmail } from '../api/profile';
|
||||
|
||||
interface WebSocketMessage {
|
||||
@@ -104,10 +105,8 @@ export function useUserNotifications(options: UseUserNotificationsOptions = {})
|
||||
wsRef.current.close();
|
||||
}
|
||||
|
||||
// Determine WebSocket host - use api subdomain for WebSocket
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsHost = `api.lvh.me:8000`; // In production, this would come from config
|
||||
const url = `${wsProtocol}//${wsHost}/ws/user/?token=${token}`;
|
||||
// Build WebSocket URL dynamically
|
||||
const url = getWebSocketUrl(`user/?token=${token}`);
|
||||
|
||||
console.log('UserNotifications WebSocket: Connecting');
|
||||
const ws = new WebSocket(url);
|
||||
|
||||
@@ -21,7 +21,7 @@ export const useUsers = () => {
|
||||
return useQuery<StaffUser[]>({
|
||||
queryKey: ['staff'],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get('/api/staff/');
|
||||
const response = await apiClient.get('/staff/');
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
@@ -35,7 +35,7 @@ export const useStaffForAssignment = () => {
|
||||
return useQuery<{ id: string; name: string; email: string; role: string }[]>({
|
||||
queryKey: ['staffForAssignment'],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get('/api/staff/');
|
||||
const response = await apiClient.get('/staff/');
|
||||
return response.data.map((user: StaffUser) => ({
|
||||
id: String(user.id),
|
||||
name: user.name || user.email, // 'name' field from serializer (full_name)
|
||||
@@ -54,7 +54,7 @@ export const usePlatformStaffForAssignment = () => {
|
||||
return useQuery<{ id: string; name: string; email: string; role: string }[]>({
|
||||
queryKey: ['platformStaffForAssignment'],
|
||||
queryFn: async () => {
|
||||
const response = await apiClient.get('/api/platform/users/');
|
||||
const response = await apiClient.get('/platform/users/');
|
||||
// Filter to only platform-level roles and format for dropdown
|
||||
const platformRoles = ['superuser', 'platform_manager', 'platform_support'];
|
||||
return response.data
|
||||
@@ -77,7 +77,7 @@ export const useUpdateStaffPermissions = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ userId, permissions }: { userId: string | number; permissions: Record<string, boolean> }) => {
|
||||
const response = await apiClient.patch(`/api/staff/${userId}/`, { permissions });
|
||||
const response = await apiClient.patch(`/staff/${userId}/`, { permissions });
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
|
||||
Reference in New Issue
Block a user