inogen/app/lib/api.ts

333 lines
7.9 KiB
TypeScript

import toast from "react-hot-toast";
interface ApiResponse<T = any> {
message: string;
data: T;
state: number;
time: number;
errorCode: number;
resultType: number;
}
class ApiService {
apiUrl = import.meta.env.VITE_API_URL;
private baseURL = this.apiUrl;
private token: string | null = null;
constructor() {
// Initialize token from localStorage
this.initializeToken();
}
private initializeToken() {
try {
const savedToken = localStorage.getItem("auth_token");
if (savedToken) {
const tokenData = JSON.parse(savedToken);
this.token = tokenData.accessToken;
}
} catch (error) {
console.error("Error initializing token:", error);
}
}
public setToken(token: string) {
this.token = token;
}
public clearToken() {
this.token = null;
}
private async request<T = any>(
endpoint: string,
options: RequestInit = {},
): Promise<ApiResponse<T>> {
const url = `${this.baseURL}${endpoint}`;
const defaultHeaders: HeadersInit = {
"Content-Type": "application/json",
};
// Add authorization header if token exists
if (this.token) {
defaultHeaders.Authorization = `Bearer ${this.token}`;
}
const config: RequestInit = {
...options,
headers: {
...defaultHeaders,
...options.headers,
},
};
try {
const response = await fetch(url, config);
const data: ApiResponse<T> = await response.json();
// Handle different response states
if (!response.ok) {
throw new Error(
data.message || `HTTP error! status: ${response.status}`,
);
}
if (data.state !== 0) {
throw new Error(data.message || "API error occurred");
}
return data;
} catch (error) {
console.error("API request failed:", error);
// Handle network errors (propagate up; UI decides how to toast)
if (error instanceof TypeError && error.message.includes("fetch")) {
const err = Object.assign(new Error("شبکه در دسترس نیست"), {
code: "NETWORK_ERROR",
});
throw err;
}
// Handle authentication errors
if (error instanceof Error && error.message.includes("401")) {
this.clearToken();
localStorage.removeItem("auth_token");
localStorage.removeItem("auth_user");
try {
sessionStorage.setItem("sessionExpired", "1");
} catch {}
window.location.href = "/login";
throw error;
}
throw error;
}
}
// POST to external absolute endpoint while reusing token handling
public async postAbsolute<T = any>(
fullUrl: string,
data?: any,
): Promise<ApiResponse<T>> {
const defaultHeaders: HeadersInit = {
"Content-Type": "application/json;charset=UTF-8",
};
if (this.token) {
defaultHeaders.Authorization = `Bearer ${this.token}`;
}
const config: RequestInit = {
method: "POST",
headers: defaultHeaders,
body: data ? JSON.stringify(data) : undefined,
};
try {
const response = await fetch(fullUrl, config);
const apiData: ApiResponse<T> = await response.json();
if (!response.ok) {
throw new Error(apiData?.message || `HTTP error! status: ${response.status}`);
}
if (typeof apiData?.state !== "undefined" && apiData.state !== 0) {
throw new Error(apiData.message || "API error occurred");
}
return apiData;
} catch (error) {
console.error("API absolute POST failed:", error);
throw error as Error;
}
}
// Innovation process function call wrapper
public async call<T = any>(payload: any) {
const url = "https://inogen-back.pelekan.org/api/call";
return this.postAbsolute<T>(url, payload);
}
// GET request
public async get<T = any>(endpoint: string): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: "GET",
});
}
// POST request
public async post<T = any>(
endpoint: string,
data?: any,
): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: "POST",
body: data ? JSON.stringify(data) : undefined,
});
}
// PUT request
public async put<T = any>(
endpoint: string,
data?: any,
): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: "PUT",
body: data ? JSON.stringify(data) : undefined,
});
}
// DELETE request
public async delete<T = any>(endpoint: string): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: "DELETE",
});
}
// PATCH request
public async patch<T = any>(
endpoint: string,
data?: any,
): Promise<ApiResponse<T>> {
return this.request<T>(endpoint, {
method: "PATCH",
body: data ? JSON.stringify(data) : undefined,
});
}
// Authentication methods
public async login(username: string, password: string) {
try {
const response = await this.post("/login", {
username,
password,
});
if (response.state === 0) {
const parsedData = JSON.parse(response.data);
this.setToken(parsedData.Token.AccessToken);
return {
success: true,
data: parsedData,
message: response.message,
};
}
return {
success: false,
message: response.message || "ورود ناموفق",
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : "خطای غیرمنتظره",
};
}
}
public async logout() {
try {
// Call logout endpoint if it exists
await this.post("/logout");
} catch (error) {
console.error("Logout API call failed:", error);
} finally {
// Clear token regardless of API call success
this.clearToken();
localStorage.removeItem("auth_token");
localStorage.removeItem("auth_user");
}
}
// Profile methods
public async getProfile() {
return this.get("/profile");
}
public async updateProfile(data: any) {
return this.put("/profile", data);
}
// Select method for dynamic data queries
public async select(
data:
| {
ProcessName: string;
OutputFields: string[];
Pagination: { PageNumber: number; PageSize: number };
Sorts: [string, string][];
Conditions: any[];
}
| any,
) {
return this.post("/select", data);
}
// Projects methods
public async getProjects() {
return this.get("/projects");
}
public async getProject(id: number) {
return this.get(`/projects/${id}`);
}
public async createProject(data: any) {
return this.post("/projects", data);
}
public async updateProject(id: number, data: any) {
return this.put(`/projects/${id}`, data);
}
public async deleteProject(id: number) {
return this.delete(`/projects/${id}`);
}
// Dashboard methods
public async getDashboardStats() {
return this.get("/dashboard/stats");
}
public async getDashboardRecentActivity() {
return this.get("/dashboard/recent-activity");
}
// File upload method
public async uploadFile(file: File, endpoint: string = "/upload") {
const formData = new FormData();
formData.append("file", file);
const config: RequestInit = {
method: "POST",
headers: this.token ? { Authorization: `Bearer ${this.token}` } : {},
body: formData,
};
try {
const response = await fetch(`${this.baseURL}${endpoint}`, config);
const data = await response.json();
if (!response.ok) {
throw new Error(
data.message || `HTTP error! status: ${response.status}`,
);
}
return data;
} catch (error) {
console.error("File upload failed:", error);
throw error;
}
}
}
// Create and export a singleton instance
const apiService = new ApiService();
export default apiService;
// Export types for use in components
export type { ApiResponse };