feat: add Layer 2 - WhatsApp Core logic, API Gateway models/auth, Frontend core
WhatsApp Core: - SessionManager with Baileys integration for multi-account support - Express server with REST API and Socket.IO for real-time events - Session lifecycle management (create, disconnect, delete) - Message sending with support for text, image, document, audio, video API Gateway: - Database models: User, WhatsAppAccount, Contact, Conversation, Message - JWT authentication with access/refresh tokens - Auth endpoints: login, refresh, register, me - Pydantic schemas for request/response validation Frontend: - React 18 app structure with routing - Zustand auth store with persistence - API client with automatic token handling - Base CSS and TypeScript declarations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
79
frontend/src/api/client.ts
Normal file
79
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || '/api';
|
||||
|
||||
interface RequestOptions extends RequestInit {
|
||||
params?: Record<string, string>;
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
private baseUrl: string;
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
private getAuthHeaders(): Record<string, string> {
|
||||
const token = localStorage.getItem('access_token');
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
}
|
||||
|
||||
private async request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
|
||||
const { params, ...fetchOptions } = options;
|
||||
|
||||
let url = `${this.baseUrl}${endpoint}`;
|
||||
if (params) {
|
||||
const searchParams = new URLSearchParams(params);
|
||||
url += `?${searchParams.toString()}`;
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...this.getAuthHeaders(),
|
||||
...(options.headers as Record<string, string>),
|
||||
};
|
||||
|
||||
const response = await fetch(url, {
|
||||
...fetchOptions,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
localStorage.removeItem('access_token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
window.location.href = '/login';
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ detail: 'Request failed' }));
|
||||
throw new Error(error.detail || 'Request failed');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
get<T>(endpoint: string, options?: RequestOptions): Promise<T> {
|
||||
return this.request<T>(endpoint, { ...options, method: 'GET' });
|
||||
}
|
||||
|
||||
post<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T> {
|
||||
return this.request<T>(endpoint, {
|
||||
...options,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
put<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T> {
|
||||
return this.request<T>(endpoint, {
|
||||
...options,
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
delete<T>(endpoint: string, options?: RequestOptions): Promise<T> {
|
||||
return this.request<T>(endpoint, { ...options, method: 'DELETE' });
|
||||
}
|
||||
}
|
||||
|
||||
export const apiClient = new ApiClient(API_BASE_URL);
|
||||
Reference in New Issue
Block a user