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>
114 lines
2.4 KiB
TypeScript
114 lines
2.4 KiB
TypeScript
/**
|
|
* Authentication API
|
|
*/
|
|
|
|
import apiClient from './client';
|
|
|
|
export interface LoginCredentials {
|
|
username: string;
|
|
password: string;
|
|
}
|
|
|
|
import { UserRole } from '../types';
|
|
|
|
export interface MasqueradeStackEntry {
|
|
user_id: number;
|
|
username: string;
|
|
role: UserRole;
|
|
business_id?: number;
|
|
business_subdomain?: string;
|
|
}
|
|
|
|
export interface LoginResponse {
|
|
access: string;
|
|
refresh: string;
|
|
user: {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
name: string;
|
|
role: UserRole;
|
|
avatar_url?: string;
|
|
email_verified?: boolean;
|
|
is_staff: boolean;
|
|
is_superuser: boolean;
|
|
business?: number;
|
|
business_name?: string;
|
|
business_subdomain?: string;
|
|
};
|
|
masquerade_stack?: MasqueradeStackEntry[];
|
|
}
|
|
|
|
export interface User {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
name: string;
|
|
role: UserRole;
|
|
avatar_url?: string;
|
|
email_verified?: boolean;
|
|
is_staff: boolean;
|
|
is_superuser: boolean;
|
|
business?: number;
|
|
business_name?: string;
|
|
business_subdomain?: string;
|
|
}
|
|
|
|
/**
|
|
* Login user
|
|
*/
|
|
export const login = async (credentials: LoginCredentials): Promise<LoginResponse> => {
|
|
const response = await apiClient.post<LoginResponse>('/api/auth/login/', credentials);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Logout user
|
|
*/
|
|
export const logout = async (): Promise<void> => {
|
|
await apiClient.post('/api/auth/logout/');
|
|
};
|
|
|
|
/**
|
|
* Get current user
|
|
*/
|
|
export const getCurrentUser = async (): Promise<User> => {
|
|
const response = await apiClient.get<User>('/api/auth/me/');
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Refresh access token
|
|
*/
|
|
export const refreshToken = async (refresh: string): Promise<{ access: string }> => {
|
|
const response = await apiClient.post('/api/auth/refresh/', { refresh });
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Masquerade as another user (hijack)
|
|
*/
|
|
export const masquerade = async (
|
|
user_pk: number,
|
|
hijack_history?: MasqueradeStackEntry[]
|
|
): Promise<LoginResponse> => {
|
|
const response = await apiClient.post<LoginResponse>(
|
|
'/api/auth/hijack/acquire/',
|
|
{ user_pk, hijack_history }
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Stop masquerading and return to previous user
|
|
*/
|
|
export const stopMasquerade = async (
|
|
masquerade_stack: MasqueradeStackEntry[]
|
|
): Promise<LoginResponse> => {
|
|
const response = await apiClient.post<LoginResponse>(
|
|
'/api/auth/hijack/release/',
|
|
{ masquerade_stack }
|
|
);
|
|
return response.data;
|
|
};
|