Files
approagency-admin-panel/lib/api/client.ts
T

123 lines
3.7 KiB
TypeScript

const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.approagency.ir/api';
interface ApiOptions extends RequestInit {
token?: string;
}
// Simple in-memory cache for ongoing requests
const pendingRequests = new Map();
class ApiClient {
private async request<T>(
endpoint: string,
options: ApiOptions = {}
): Promise<T> {
const { token, ...fetchOptions } = options;
const headers: Record<string, string> = {
// Always ask for JSON so Laravel returns 401 JSON on auth failure
// instead of a 302 redirect to the login page.
Accept: 'application/json',
...(options.headers as Record<string, string> || {}),
};
// Don't set Content-Type for FormData (browser will set it automatically with boundary)
if (!(options.body instanceof FormData)) {
headers['Content-Type'] = 'application/json';
}
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
// Create a unique key for this request
const requestKey = `${fetchOptions.method || 'GET'}-${endpoint}-${JSON.stringify(options.body)}`;
// Check if there's already a pending request with the same key
if (pendingRequests.has(requestKey)) {
return pendingRequests.get(requestKey);
}
// Make the request
const promise = fetch(`${API_BASE_URL}${endpoint}`, {
...fetchOptions,
headers,
}).then(async (response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || 'API request failed');
}
return response.json();
}).finally(() => {
// Clean up after request completes
pendingRequests.delete(requestKey);
});
// Store the promise
pendingRequests.set(requestKey, promise);
return promise;
}
async post<T>(endpoint: string, data: any, token?: string): Promise<T> {
const body = data instanceof FormData ? data : JSON.stringify(data);
return this.request<T>(endpoint, {
method: 'POST',
body,
token,
});
}
async get<T>(endpoint: string, token?: string): Promise<T> {
return this.request<T>(endpoint, {
method: 'GET',
token,
});
}
async put<T>(endpoint: string, data: any, token?: string): Promise<T> {
const body = data instanceof FormData ? data : JSON.stringify(data);
return this.request<T>(endpoint, {
method: 'PUT',
body,
token,
});
}
async patch<T>(endpoint: string, data: any, token?: string): Promise<T> {
const body = data instanceof FormData ? data : JSON.stringify(data);
return this.request<T>(endpoint, {
method: 'PATCH',
body,
token,
});
}
async delete<T>(endpoint: string, data?: any, token?: string): Promise<T> {
const options: ApiOptions = {
method: 'DELETE',
token,
};
if (data) {
options.body = data instanceof FormData ? data : JSON.stringify(data);
if (!(data instanceof FormData)) {
options.headers = {
'Content-Type': 'application/json',
};
}
}
return this.request<T>(endpoint, options);
}
async deleteWithBody<T>(endpoint: string, data: any, token?: string): Promise<T> {
return this.delete<T>(endpoint, data, token);
}
}
export const apiClient = new ApiClient();