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:
poduck
2025-11-30 15:27:57 -05:00
parent f1d4dac9d2
commit 4cd6610f2a
53 changed files with 476 additions and 687 deletions

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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({

View File

@@ -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;

View File

@@ -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

View File

@@ -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'] });

View File

@@ -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;
},
});

View File

@@ -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: () => {

View File

@@ -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'] });

View File

@@ -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'] });

View File

@@ -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: () => {

View File

@@ -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);

View File

@@ -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: () => {