- Add BroadcastMessage and MessageRecipient models for sending messages to groups or individuals - Add Messages page with compose form and sent messages list - Support targeting by role (owners, managers, staff, customers) or individual users - Add can_send_messages permission (owners always, managers by default with revocable permission) - Add autofill search dropdown with infinite scroll for selecting individual recipients - Add staff permission toggle for managers' messaging access - Integrate Messages link in sidebar for users with permission 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
147 lines
3.3 KiB
TypeScript
147 lines
3.3 KiB
TypeScript
/**
|
|
* Authentication API
|
|
*/
|
|
|
|
import apiClient from './client';
|
|
|
|
export interface LoginCredentials {
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
import { UserRole } from '../types';
|
|
|
|
export interface QuotaOverage {
|
|
id: number;
|
|
quota_type: string;
|
|
display_name: string;
|
|
current_usage: number;
|
|
allowed_limit: number;
|
|
overage_amount: number;
|
|
days_remaining: number;
|
|
grace_period_ends_at: string;
|
|
}
|
|
|
|
export interface MasqueradeStackEntry {
|
|
user_id: number;
|
|
username: string;
|
|
role: UserRole;
|
|
business_id?: number;
|
|
business_subdomain?: string;
|
|
}
|
|
|
|
export interface LoginResponse {
|
|
// Regular login success
|
|
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;
|
|
can_send_messages?: boolean;
|
|
};
|
|
masquerade_stack?: MasqueradeStackEntry[];
|
|
// MFA challenge response
|
|
mfa_required?: boolean;
|
|
user_id?: number;
|
|
mfa_methods?: ('SMS' | 'TOTP' | 'BACKUP')[];
|
|
phone_last_4?: string;
|
|
}
|
|
|
|
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;
|
|
permissions?: Record<string, boolean>;
|
|
can_invite_staff?: boolean;
|
|
can_access_tickets?: boolean;
|
|
can_edit_schedule?: boolean;
|
|
can_send_messages?: boolean;
|
|
linked_resource_id?: number;
|
|
quota_overages?: QuotaOverage[];
|
|
}
|
|
|
|
/**
|
|
* Login user
|
|
*/
|
|
export const login = async (credentials: LoginCredentials): Promise<LoginResponse> => {
|
|
const response = await apiClient.post<LoginResponse>('/auth/login/', credentials);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Logout user
|
|
*/
|
|
export const logout = async (): Promise<void> => {
|
|
await apiClient.post('/auth/logout/');
|
|
};
|
|
|
|
/**
|
|
* Get current user
|
|
*/
|
|
export const getCurrentUser = async (): Promise<User> => {
|
|
const response = await apiClient.get<User>('/auth/me/');
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Refresh access token
|
|
*/
|
|
export const refreshToken = async (refresh: string): Promise<{ access: string }> => {
|
|
const response = await apiClient.post('/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>(
|
|
'/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>(
|
|
'/auth/hijack/release/',
|
|
{ masquerade_stack }
|
|
);
|
|
return response.data;
|
|
};
|
|
|
|
/**
|
|
* Request password reset email
|
|
*/
|
|
export const forgotPassword = async (email: string): Promise<{ message: string }> => {
|
|
const response = await apiClient.post<{ message: string }>('/auth/password-reset/', { email });
|
|
return response.data;
|
|
};
|